feat: add i18n to admin panel (#1326)
Co-authored-by: shikhantmaungs <shinkhantmaungs@gmail.com> Co-authored-by: Thomas Ghysels <info@thomasg.be> Co-authored-by: Kokutse Djoguenou <kokutse@Kokutses-MacBook-Pro.local> Co-authored-by: Christian Gil <47041342+ChrisGV04@users.noreply.github.com> Co-authored-by: Łukasz Rabiec <lukaszrabiec@gmail.com> Co-authored-by: Jenny <jennifer.eberlei@gmail.com> Co-authored-by: Hung Vu <hunghvu2017@gmail.com> Co-authored-by: Shin Khant Maung <101539335+shinkhantmaungs@users.noreply.github.com> Co-authored-by: Carlo Brualdi <carlo.brualdi@gmail.com> Co-authored-by: Ariel Tonglet <ariel.tonglet@gmail.com> Co-authored-by: Roman Ryzhikov <general+github@ya.ru> Co-authored-by: maekoya <maekoya@stromatolite.jp> Co-authored-by: Emilia Trollros <3m1l1a@emiliatrollros.se> Co-authored-by: Kokutse J Djoguenou <90865585+Julesdj@users.noreply.github.com> Co-authored-by: Mitch Dries <mitch.dries@gmail.com> BREAKING CHANGE: If you assigned labels to collections, globals or block names, you need to update your config! Your GraphQL schema and generated Typescript interfaces may have changed. Payload no longer uses labels for code based naming. To prevent breaking changes to your GraphQL API and typescript types in your project, you can assign the below properties to match what Payload previously generated for you from labels. On Collections Use `graphQL.singularName`, `graphQL.pluralName` for GraphQL schema names. Use `typescript.interface` for typescript generation name. On Globals Use `graphQL.name` for GraphQL Schema name. Use `typescript.interface` for typescript generation name. On Blocks (within Block fields) Use `graphQL.singularName` for graphQL schema names.
This commit is contained in:
@@ -20,7 +20,7 @@ const sanitizeFields = (fields: Field[], validRelationships: string[]): Field[]
|
||||
}
|
||||
|
||||
// Auto-label
|
||||
if ('name' in field && field.name && typeof field.label !== 'string' && field.label !== false) {
|
||||
if ('name' in field && field.name && typeof field.label !== 'object' && typeof field.label !== 'string' && field.label !== false) {
|
||||
field.label = toWords(field.name);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const sanitizeFields = (fields: Field[], validRelationships: string[]): Field[]
|
||||
field.fields.push(baseIDField);
|
||||
}
|
||||
|
||||
if ((field.type === 'blocks' || field.type === 'array') && field.label !== false) {
|
||||
if ((field.type === 'blocks' || field.type === 'array') && field.label) {
|
||||
field.labels = field.labels || formatLabels(field.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export const baseAdminComponentFields = joi.object().keys({
|
||||
export const baseAdminFields = joi.object().keys({
|
||||
description: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
componentSchema,
|
||||
),
|
||||
position: joi.string().valid('sidebar'),
|
||||
@@ -26,6 +27,7 @@ export const baseAdminFields = joi.object().keys({
|
||||
|
||||
export const baseField = joi.object().keys({
|
||||
label: joi.alternatives().try(
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
joi.string(),
|
||||
joi.valid(false),
|
||||
),
|
||||
@@ -68,7 +70,10 @@ export const text = baseField.keys({
|
||||
minLength: joi.number(),
|
||||
maxLength: joi.number(),
|
||||
admin: baseAdminFields.keys({
|
||||
placeholder: joi.string(),
|
||||
placeholder: joi.alternatives().try(
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
joi.string(),
|
||||
),
|
||||
autoComplete: joi.string(),
|
||||
}),
|
||||
});
|
||||
@@ -139,7 +144,10 @@ export const select = baseField.keys({
|
||||
joi.string(),
|
||||
joi.object({
|
||||
value: joi.string().required().allow(''),
|
||||
label: joi.string().required(),
|
||||
label: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
}),
|
||||
),
|
||||
).required(),
|
||||
@@ -163,7 +171,10 @@ export const radio = baseField.keys({
|
||||
joi.string(),
|
||||
joi.object({
|
||||
value: joi.string().required().allow(''),
|
||||
label: joi.string().required(),
|
||||
label: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
).required(),
|
||||
}),
|
||||
),
|
||||
).required(),
|
||||
@@ -195,7 +206,10 @@ export const collapsible = baseField.keys({
|
||||
const tab = baseField.keys({
|
||||
name: joi.string().when('localized', { is: joi.exist(), then: joi.required() }),
|
||||
localized: joi.boolean(),
|
||||
label: joi.string().required(),
|
||||
label: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
).required(),
|
||||
fields: joi.array().items(joi.link('#field')).required(),
|
||||
description: joi.alternatives().try(
|
||||
joi.string(),
|
||||
@@ -234,8 +248,14 @@ export const array = baseField.keys({
|
||||
maxRows: joi.number(),
|
||||
fields: joi.array().items(joi.link('#field')).required(),
|
||||
labels: joi.object({
|
||||
singular: joi.string(),
|
||||
plural: joi.string(),
|
||||
singular: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
plural: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(
|
||||
joi.array().items(joi.object()),
|
||||
@@ -304,17 +324,32 @@ export const blocks = baseField.keys({
|
||||
maxRows: joi.number(),
|
||||
name: joi.string().required(),
|
||||
labels: joi.object({
|
||||
singular: joi.string(),
|
||||
plural: joi.string(),
|
||||
singular: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
plural: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
}),
|
||||
blocks: joi.array().items(
|
||||
joi.object({
|
||||
slug: joi.string().required(),
|
||||
imageURL: joi.string(),
|
||||
imageAltText: joi.string(),
|
||||
graphQL: joi.object().keys({
|
||||
singularName: joi.string(),
|
||||
}),
|
||||
labels: joi.object({
|
||||
singular: joi.string(),
|
||||
plural: joi.string(),
|
||||
singular: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
plural: joi.alternatives().try(
|
||||
joi.string(),
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
),
|
||||
}),
|
||||
fields: joi.array().items(joi.link('#field')),
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { CSSProperties } from 'react';
|
||||
import { Editor } from 'slate';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { Operation, Where } from '../../types';
|
||||
import { TypeWithID } from '../../collections/config/types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
@@ -72,8 +73,8 @@ type Admin = {
|
||||
}
|
||||
|
||||
export type Labels = {
|
||||
singular: string;
|
||||
plural: string;
|
||||
singular: Record<string, string> | string;
|
||||
plural: Record<string, string> | string;
|
||||
};
|
||||
|
||||
export type ValidateOptions<T, S, F> = {
|
||||
@@ -83,12 +84,13 @@ export type ValidateOptions<T, S, F> = {
|
||||
user?: Partial<User>
|
||||
operation?: Operation
|
||||
payload?: Payload
|
||||
t: TFunction
|
||||
} & F;
|
||||
|
||||
export type Validate<T = any, S = any, F = any> = (value?: T, options?: ValidateOptions<F, S, Partial<F>>) => string | true | Promise<string | true>;
|
||||
|
||||
export type OptionObject = {
|
||||
label: string
|
||||
label: Record<string, string> | string
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -96,7 +98,7 @@ export type Option = OptionObject | string
|
||||
|
||||
export interface FieldBase {
|
||||
name: string;
|
||||
label?: string | false;
|
||||
label?: Record<string, string> | string | false;
|
||||
required?: boolean;
|
||||
unique?: boolean;
|
||||
index?: boolean;
|
||||
@@ -123,7 +125,7 @@ export type NumberField = FieldBase & {
|
||||
type: 'number';
|
||||
admin?: Admin & {
|
||||
autoComplete?: string
|
||||
placeholder?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
step?: number
|
||||
}
|
||||
min?: number
|
||||
@@ -135,7 +137,7 @@ export type TextField = FieldBase & {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
admin?: Admin & {
|
||||
placeholder?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
autoComplete?: string
|
||||
}
|
||||
}
|
||||
@@ -143,7 +145,7 @@ export type TextField = FieldBase & {
|
||||
export type EmailField = FieldBase & {
|
||||
type: 'email';
|
||||
admin?: Admin & {
|
||||
placeholder?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
autoComplete?: string
|
||||
}
|
||||
}
|
||||
@@ -153,7 +155,7 @@ export type TextareaField = FieldBase & {
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
admin?: Admin & {
|
||||
placeholder?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
rows?: number
|
||||
}
|
||||
}
|
||||
@@ -165,7 +167,7 @@ export type CheckboxField = FieldBase & {
|
||||
export type DateField = FieldBase & {
|
||||
type: 'date';
|
||||
admin?: Admin & {
|
||||
placeholder?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
date?: ConditionalDateProps
|
||||
}
|
||||
}
|
||||
@@ -205,7 +207,7 @@ type TabBase = {
|
||||
export type NamedTab = TabBase & FieldBase
|
||||
|
||||
export type UnnamedTab = TabBase & Omit<FieldBase, 'name'> & {
|
||||
label: string
|
||||
label: Record<string, string> | string
|
||||
localized?: never
|
||||
}
|
||||
|
||||
@@ -224,7 +226,7 @@ export type TabAsField = Tab & {
|
||||
|
||||
export type UIField = {
|
||||
name: string
|
||||
label?: string
|
||||
label?: Record<string, string> | string
|
||||
admin: {
|
||||
position?: string
|
||||
width?: string
|
||||
@@ -313,7 +315,7 @@ export type RichTextLeaf = 'bold' | 'italic' | 'underline' | 'strikethrough' | '
|
||||
export type RichTextField = FieldBase & {
|
||||
type: 'richText';
|
||||
admin?: Admin & {
|
||||
placeholder?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
elements?: RichTextElement[];
|
||||
leaves?: RichTextLeaf[];
|
||||
hideGutter?: boolean
|
||||
@@ -358,6 +360,9 @@ export type Block = {
|
||||
fields: Field[];
|
||||
imageURL?: string;
|
||||
imageAltText?: string;
|
||||
graphQL?: {
|
||||
singularName?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type BlockField = FieldBase & {
|
||||
|
||||
@@ -49,7 +49,7 @@ export const beforeChange = async ({
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(errors);
|
||||
throw new ValidationError(errors, req.t);
|
||||
}
|
||||
|
||||
mergeLocaleActions.forEach((action) => action());
|
||||
|
||||
@@ -112,6 +112,7 @@ export const promise = async ({
|
||||
operation,
|
||||
user: req.user,
|
||||
payload: req.payload,
|
||||
t: req.t,
|
||||
});
|
||||
|
||||
if (typeof validationResult === 'string') {
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { text, textarea, password, select, point, number } from './validations';
|
||||
import { ValidateOptions } from './config/types';
|
||||
|
||||
const minLengthMessage = (length: number) => `This value must be longer than the minimum length of ${length} characters.`;
|
||||
const maxLengthMessage = (length: number) => `This value must be shorter than the max length of ${length} characters.`;
|
||||
const minValueMessage = (value: number, min: number) => `"${value}" is less than the min allowed value of ${min}.`;
|
||||
const maxValueMessage = (value: number, max: number) => `"${value}" is greater than the max allowed value of ${max}.`;
|
||||
const requiredMessage = 'This field is required.';
|
||||
const validNumberMessage = 'Please enter a valid number.';
|
||||
const t = jest.fn((string) => string);
|
||||
|
||||
let options: ValidateOptions<any, any, any> = {
|
||||
operation: 'create',
|
||||
data: undefined,
|
||||
siblingData: undefined,
|
||||
t,
|
||||
};
|
||||
|
||||
describe('Field Validations', () => {
|
||||
@@ -23,7 +20,7 @@ describe('Field Validations', () => {
|
||||
it('should show required message', () => {
|
||||
const val = undefined;
|
||||
const result = text(val, { ...options, required: true });
|
||||
expect(result).toBe(requiredMessage);
|
||||
expect(result).toBe('validation:required');
|
||||
});
|
||||
it('should handle undefined', () => {
|
||||
const val = undefined;
|
||||
@@ -33,12 +30,12 @@ describe('Field Validations', () => {
|
||||
it('should validate maxLength', () => {
|
||||
const val = 'toolong';
|
||||
const result = text(val, { ...options, maxLength: 5 });
|
||||
expect(result).toBe(maxLengthMessage(5));
|
||||
expect(result).toBe('validation:shorterThanMax');
|
||||
});
|
||||
it('should validate minLength', () => {
|
||||
const val = 'short';
|
||||
const result = text(val, { ...options, minLength: 10 });
|
||||
expect(result).toBe(minLengthMessage(10));
|
||||
expect(result).toBe('validation:longerThanMin');
|
||||
});
|
||||
it('should validate maxLength with no value', () => {
|
||||
const val = undefined;
|
||||
@@ -62,7 +59,7 @@ describe('Field Validations', () => {
|
||||
it('should show required message', () => {
|
||||
const val = undefined;
|
||||
const result = textarea(val, { ...options, required: true });
|
||||
expect(result).toBe(requiredMessage);
|
||||
expect(result).toBe('validation:required');
|
||||
});
|
||||
|
||||
it('should handle undefined', () => {
|
||||
@@ -73,13 +70,13 @@ describe('Field Validations', () => {
|
||||
it('should validate maxLength', () => {
|
||||
const val = 'toolong';
|
||||
const result = textarea(val, { ...options, maxLength: 5 });
|
||||
expect(result).toBe(maxLengthMessage(5));
|
||||
expect(result).toBe('validation:shorterThanMax');
|
||||
});
|
||||
|
||||
it('should validate minLength', () => {
|
||||
const val = 'short';
|
||||
const result = textarea(val, { ...options, minLength: 10 });
|
||||
expect(result).toBe(minLengthMessage(10));
|
||||
expect(result).toBe('validation:longerThanMin');
|
||||
});
|
||||
it('should validate maxLength with no value', () => {
|
||||
const val = undefined;
|
||||
@@ -104,7 +101,7 @@ describe('Field Validations', () => {
|
||||
it('should show required message', () => {
|
||||
const val = undefined;
|
||||
const result = password(val, { ...options, required: true });
|
||||
expect(result).toBe(requiredMessage);
|
||||
expect(result).toBe('validation:required');
|
||||
});
|
||||
it('should handle undefined', () => {
|
||||
const val = undefined;
|
||||
@@ -114,12 +111,12 @@ describe('Field Validations', () => {
|
||||
it('should validate maxLength', () => {
|
||||
const val = 'toolong';
|
||||
const result = password(val, { ...options, maxLength: 5 });
|
||||
expect(result).toBe(maxLengthMessage(5));
|
||||
expect(result).toBe('validation:shorterThanMax');
|
||||
});
|
||||
it('should validate minLength', () => {
|
||||
const val = 'short';
|
||||
const result = password(val, { ...options, minLength: 10 });
|
||||
expect(result).toBe(minLengthMessage(10));
|
||||
expect(result).toBe('validation:longerThanMin');
|
||||
});
|
||||
it('should validate maxLength with no value', () => {
|
||||
const val = undefined;
|
||||
@@ -333,7 +330,7 @@ describe('Field Validations', () => {
|
||||
it('should show invalid number message', () => {
|
||||
const val = 'test';
|
||||
const result = number(val, { ...options });
|
||||
expect(result).toBe(validNumberMessage);
|
||||
expect(result).toBe('validation:enterNumber');
|
||||
});
|
||||
it('should handle empty value', () => {
|
||||
const val = '';
|
||||
@@ -343,17 +340,17 @@ describe('Field Validations', () => {
|
||||
it('should handle required value', () => {
|
||||
const val = '';
|
||||
const result = number(val, { ...options, required: true });
|
||||
expect(result).toBe(validNumberMessage);
|
||||
expect(result).toBe('validation:enterNumber');
|
||||
});
|
||||
it('should validate minValue', () => {
|
||||
const val = 2.4;
|
||||
const result = number(val, { ...options, min: 2.5 });
|
||||
expect(result).toBe(minValueMessage(val, 2.5));
|
||||
expect(result).toBe('validation:lessThanMin');
|
||||
});
|
||||
it('should validate maxValue', () => {
|
||||
const val = 1.25;
|
||||
const result = number(val, { ...options, max: 1 });
|
||||
expect(result).toBe(maxValueMessage(val, 1));
|
||||
expect(result).toBe('validation:greaterThanMax');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,83 +24,82 @@ import canUseDOM from '../utilities/canUseDOM';
|
||||
import { isValidID } from '../utilities/isValidID';
|
||||
import { getIDType } from '../utilities/getIDType';
|
||||
|
||||
const defaultMessage = 'This field is required.';
|
||||
|
||||
export const number: Validate<unknown, unknown, NumberField> = (value: string, { required, min, max }) => {
|
||||
export const number: Validate<unknown, unknown, NumberField> = (value: string, { t, required, min, max }) => {
|
||||
const parsedValue = parseFloat(value);
|
||||
|
||||
if ((value && typeof parsedValue !== 'number') || (required && Number.isNaN(parsedValue)) || (value && Number.isNaN(parsedValue))) {
|
||||
return 'Please enter a valid number.';
|
||||
return t('validation:enterNumber');
|
||||
}
|
||||
|
||||
if (typeof max === 'number' && parsedValue > max) {
|
||||
return `"${value}" is greater than the max allowed value of ${max}.`;
|
||||
return t('validation:greaterThanMax', { value, max });
|
||||
}
|
||||
|
||||
if (typeof min === 'number' && parsedValue < min) {
|
||||
return `"${value}" is less than the min allowed value of ${min}.`;
|
||||
return t('validation:lessThanMin', { value, min });
|
||||
}
|
||||
|
||||
if (required && typeof parsedValue !== 'number') {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const text: Validate<unknown, unknown, TextField> = (value: string, { minLength, maxLength: fieldMaxLength, required, payload }) => {
|
||||
export const text: Validate<unknown, unknown, TextField> = (value: string, { t, minLength, maxLength: fieldMaxLength, required, payload }) => {
|
||||
let maxLength: number;
|
||||
|
||||
if (typeof payload?.config?.defaultMaxTextLength === 'number') maxLength = payload.config.defaultMaxTextLength;
|
||||
if (typeof fieldMaxLength === 'number') maxLength = fieldMaxLength;
|
||||
if (value && maxLength && value.length > maxLength) {
|
||||
return `This value must be shorter than the max length of ${maxLength} characters.`;
|
||||
return t('validation:shorterThanMax', { maxLength });
|
||||
}
|
||||
|
||||
if (value && minLength && value?.length < minLength) {
|
||||
return `This value must be longer than the minimum length of ${minLength} characters.`;
|
||||
return t('validation:longerThanMin', { minLength });
|
||||
}
|
||||
|
||||
if (required) {
|
||||
if (typeof value !== 'string' || value?.length === 0) {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const password: Validate<unknown, unknown, TextField> = (value: string, { required, maxLength: fieldMaxLength, minLength, payload }) => {
|
||||
export const password: Validate<unknown, unknown, TextField> = (value: string, { t, required, maxLength: fieldMaxLength, minLength, payload }) => {
|
||||
let maxLength: number;
|
||||
|
||||
if (typeof payload?.config?.defaultMaxTextLength === 'number') maxLength = payload.config.defaultMaxTextLength;
|
||||
if (typeof fieldMaxLength === 'number') maxLength = fieldMaxLength;
|
||||
|
||||
if (value && maxLength && value.length > maxLength) {
|
||||
return `This value must be shorter than the max length of ${maxLength} characters.`;
|
||||
return t('validation:shorterThanMax', { maxLength });
|
||||
}
|
||||
|
||||
if (value && minLength && value.length < minLength) {
|
||||
return `This value must be longer than the minimum length of ${minLength} characters.`;
|
||||
return t('validation:longerThanMin', { minLength });
|
||||
}
|
||||
|
||||
if (required && !value) {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const email: Validate<unknown, unknown, EmailField> = (value: string, { required }) => {
|
||||
export const email: Validate<unknown, unknown, EmailField> = (value: string, { t, required }) => {
|
||||
if ((value && !/\S+@\S+\.\S+/.test(value))
|
||||
|| (!value && required)) {
|
||||
return 'Please enter a valid email address.';
|
||||
return t('validation:emailAddress');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const textarea: Validate<unknown, unknown, TextareaField> = (value: string, {
|
||||
t,
|
||||
required,
|
||||
maxLength: fieldMaxLength,
|
||||
minLength,
|
||||
@@ -111,64 +110,64 @@ export const textarea: Validate<unknown, unknown, TextareaField> = (value: strin
|
||||
if (typeof payload?.config?.defaultMaxTextLength === 'number') maxLength = payload.config.defaultMaxTextLength;
|
||||
if (typeof fieldMaxLength === 'number') maxLength = fieldMaxLength;
|
||||
if (value && maxLength && value.length > maxLength) {
|
||||
return `This value must be shorter than the max length of ${maxLength} characters.`;
|
||||
return t('validation:shorterThanMax', { maxLength });
|
||||
}
|
||||
|
||||
if (value && minLength && value.length < minLength) {
|
||||
return `This value must be longer than the minimum length of ${minLength} characters.`;
|
||||
return t('validation:longerThanMin', { minLength });
|
||||
}
|
||||
|
||||
if (required && !value) {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const code: Validate<unknown, unknown, CodeField> = (value: string, { required }) => {
|
||||
export const code: Validate<unknown, unknown, CodeField> = (value: string, { t, required }) => {
|
||||
if (required && value === undefined) {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const richText: Validate<unknown, unknown, RichTextField> = (value, { required }) => {
|
||||
export const richText: Validate<unknown, unknown, RichTextField> = (value, { t, required }) => {
|
||||
if (required) {
|
||||
const stringifiedDefaultValue = JSON.stringify(defaultRichTextValue);
|
||||
if (value && JSON.stringify(value) !== stringifiedDefaultValue) return true;
|
||||
return 'This field is required.';
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const checkbox: Validate<unknown, unknown, CheckboxField> = (value: boolean, { required }) => {
|
||||
export const checkbox: Validate<unknown, unknown, CheckboxField> = (value: boolean, { t, required }) => {
|
||||
if ((value && typeof value !== 'boolean')
|
||||
|| (required && typeof value !== 'boolean')) {
|
||||
return 'This field can only be equal to true or false.';
|
||||
return t('validation:trueOrFalse');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const date: Validate<unknown, unknown, DateField> = (value, { required }) => {
|
||||
export const date: Validate<unknown, unknown, DateField> = (value, { t, required }) => {
|
||||
if (value && !isNaN(Date.parse(value.toString()))) { /* eslint-disable-line */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
return `"${value}" is not a valid date.`;
|
||||
return t('validation:notValidDate', { value });
|
||||
}
|
||||
|
||||
if (required) {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateFilterOptions: Validate = async (value, { filterOptions, id, user, data, siblingData, relationTo, payload }) => {
|
||||
const validateFilterOptions: Validate = async (value, { t, filterOptions, id, user, data, siblingData, relationTo, payload }) => {
|
||||
if (!canUseDOM && typeof filterOptions !== 'undefined' && value) {
|
||||
const options: {
|
||||
[collection: string]: (string | number)[]
|
||||
@@ -235,7 +234,7 @@ const validateFilterOptions: Validate = async (value, { filterOptions, id, user,
|
||||
if (invalidRelationships.length > 0) {
|
||||
return invalidRelationships.reduce((err, invalid, i) => {
|
||||
return `${err} ${JSON.stringify(invalid)}${invalidRelationships.length === i + 1 ? ',' : ''} `;
|
||||
}, 'This field has the following invalid selections:') as string;
|
||||
}, t('validation:invalidSelections')) as string;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -246,7 +245,7 @@ const validateFilterOptions: Validate = async (value, { filterOptions, id, user,
|
||||
|
||||
export const upload: Validate<unknown, unknown, UploadField> = (value: string, options) => {
|
||||
if (!value && options.required) {
|
||||
return defaultMessage;
|
||||
return options.t('validation:required');
|
||||
}
|
||||
|
||||
if (!canUseDOM && typeof value !== 'undefined' && value !== null) {
|
||||
@@ -254,7 +253,7 @@ export const upload: Validate<unknown, unknown, UploadField> = (value: string, o
|
||||
const type = getIDType(idField);
|
||||
|
||||
if (!isValidID(value, type)) {
|
||||
return 'This field is not a valid upload ID';
|
||||
return options.t('validation:validUploadID');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +262,7 @@ export const upload: Validate<unknown, unknown, UploadField> = (value: string, o
|
||||
|
||||
export const relationship: Validate<unknown, unknown, RelationshipField> = async (value: RelationshipValue, options) => {
|
||||
if ((!value || (Array.isArray(value) && value.length === 0)) && options.required) {
|
||||
return defaultMessage;
|
||||
return options.t('validation:required');
|
||||
}
|
||||
|
||||
if (!canUseDOM && typeof value !== 'undefined' && value !== null) {
|
||||
@@ -308,63 +307,63 @@ export const relationship: Validate<unknown, unknown, RelationshipField> = async
|
||||
return validateFilterOptions(value, options);
|
||||
};
|
||||
|
||||
export const array: Validate<unknown, unknown, ArrayField> = (value, { minRows, maxRows, required }) => {
|
||||
export const array: Validate<unknown, unknown, ArrayField> = (value, { t, minRows, maxRows, required }) => {
|
||||
if (minRows && value < minRows) {
|
||||
return `This field requires at least ${minRows} row(s).`;
|
||||
return t('validation:requiresAtLeast', { count: minRows, label: t('rows') });
|
||||
}
|
||||
|
||||
if (maxRows && value > maxRows) {
|
||||
return `This field requires no more than ${maxRows} row(s).`;
|
||||
return t('validation:requiresNoMoreThan', { count: maxRows, label: t('rows') });
|
||||
}
|
||||
|
||||
if (!value && required) {
|
||||
return 'This field requires at least one row.';
|
||||
return t('validation:requiresAtLeast', { count: 1, label: t('row') });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const select: Validate<unknown, unknown, SelectField> = (value, { options, hasMany, required }) => {
|
||||
export const select: Validate<unknown, unknown, SelectField> = (value, { t, options, hasMany, required }) => {
|
||||
if (Array.isArray(value) && value.some((input) => !options.some((option) => (option === input || (typeof option !== 'string' && option?.value === input))))) {
|
||||
return 'This field has an invalid selection';
|
||||
return t('validation:invalidSelection');
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && !options.some((option) => (option === value || (typeof option !== 'string' && option.value === value)))) {
|
||||
return 'This field has an invalid selection';
|
||||
return t('validation:invalidSelection');
|
||||
}
|
||||
|
||||
if (required && (
|
||||
(typeof value === 'undefined' || value === null) || (hasMany && Array.isArray(value) && (value as [])?.length === 0))
|
||||
) {
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const radio: Validate<unknown, unknown, RadioField> = (value, { options, required }) => {
|
||||
export const radio: Validate<unknown, unknown, RadioField> = (value, { t, options, required }) => {
|
||||
const stringValue = String(value);
|
||||
if ((typeof value !== 'undefined' || !required) && (options.find((option) => String(typeof option !== 'string' && option?.value) === stringValue))) return true;
|
||||
return defaultMessage;
|
||||
return t('validation:required');
|
||||
};
|
||||
|
||||
export const blocks: Validate<unknown, unknown, BlockField> = (value, { maxRows, minRows, required }) => {
|
||||
export const blocks: Validate<unknown, unknown, BlockField> = (value, { t, maxRows, minRows, required }) => {
|
||||
if (minRows && value < minRows) {
|
||||
return `This field requires at least ${minRows} row(s).`;
|
||||
return t('validation:requiresAtLeast', { count: minRows, label: t('rows') });
|
||||
}
|
||||
|
||||
if (maxRows && value > maxRows) {
|
||||
return `This field requires no more than ${maxRows} row(s).`;
|
||||
return t('validation:requiresNoMoreThan', { count: maxRows, label: t('rows') });
|
||||
}
|
||||
|
||||
if (!value && required) {
|
||||
return 'This field requires at least one row.';
|
||||
return t('validation:requiresAtLeast', { count: 1, label: t('row') });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const point: Validate<unknown, unknown, PointField> = (value: [number | string, number | string] = ['', ''], { required }) => {
|
||||
export const point: Validate<unknown, unknown, PointField> = (value: [number | string, number | string] = ['', ''], { t, required }) => {
|
||||
const lng = parseFloat(String(value[0]));
|
||||
const lat = parseFloat(String(value[1]));
|
||||
if (required && (
|
||||
@@ -372,11 +371,11 @@ export const point: Validate<unknown, unknown, PointField> = (value: [number | s
|
||||
|| (Number.isNaN(lng) || Number.isNaN(lat))
|
||||
|| (Array.isArray(value) && value.length !== 2)
|
||||
)) {
|
||||
return 'This field requires two numbers';
|
||||
return t('validation:requiresTwoNumbers');
|
||||
}
|
||||
|
||||
if ((value[1] && Number.isNaN(lng)) || (value[0] && Number.isNaN(lat))) {
|
||||
return 'This field has an invalid input';
|
||||
return t('validation:invalidInput');
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user