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:
Dan Ribbens
2022-11-18 07:36:30 -05:00
committed by GitHub
parent c49ee15b6a
commit bab34d82f5
279 changed files with 9547 additions and 3242 deletions

View File

@@ -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);
}

View File

@@ -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')),
}),

View File

@@ -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 & {

View File

@@ -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());

View File

@@ -112,6 +112,7 @@ export const promise = async ({
operation,
user: req.user,
payload: req.payload,
t: req.t,
});
if (typeof validationResult === 'string') {

View File

@@ -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');
});
});
});

View File

@@ -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;