diff --git a/packages/nc-gui/components/dlg/AirtableImport.vue b/packages/nc-gui/components/dlg/AirtableImport.vue index 0aeca558e4..f5278df1cf 100644 --- a/packages/nc-gui/components/dlg/AirtableImport.vue +++ b/packages/nc-gui/components/dlg/AirtableImport.vue @@ -65,8 +65,8 @@ const syncSource = ref({ }) const validators = computed(() => ({ - 'details.apiKey': [fieldRequiredValidator], - 'details.syncSourceUrlOrId': [fieldRequiredValidator], + 'details.apiKey': [fieldRequiredValidator()], + 'details.syncSourceUrlOrId': [fieldRequiredValidator()], })) const dialogShow = computed({ diff --git a/packages/nc-gui/components/dlg/QuickImport.vue b/packages/nc-gui/components/dlg/QuickImport.vue index 4d98ffbff2..d7e2567063 100644 --- a/packages/nc-gui/components/dlg/QuickImport.vue +++ b/packages/nc-gui/components/dlg/QuickImport.vue @@ -70,8 +70,8 @@ const isImportTypeCsv = computed(() => importType === 'csv') const IsImportTypeExcel = computed(() => importType === 'excel') const validators = computed(() => ({ - url: [fieldRequiredValidator, importUrlValidator, isImportTypeCsv.value ? importCsvUrlValidator : importExcelUrlValidator], - maxRowsToParse: [fieldRequiredValidator], + url: [fieldRequiredValidator(), importUrlValidator, isImportTypeCsv.value ? importCsvUrlValidator : importExcelUrlValidator], + maxRowsToParse: [fieldRequiredValidator()], })) const { validate, validateInfos } = useForm(importState, validators) diff --git a/packages/nc-gui/components/template/Editor.vue b/packages/nc-gui/components/template/Editor.vue index 0a3b81523e..f0d7dbd6c6 100644 --- a/packages/nc-gui/components/template/Editor.vue +++ b/packages/nc-gui/components/template/Editor.vue @@ -94,13 +94,13 @@ const data = reactive<{ title: string | null; name: string; tables: (TableType & }) const validators = computed(() => - data.tables.reduce>((acc, table, tableIdx) => { - acc[`tables.${tableIdx}.ref_table_name`] = [fieldRequiredValidator] + data.tables.reduce]>>((acc, table, tableIdx) => { + acc[`tables.${tableIdx}.ref_table_name`] = [fieldRequiredValidator()] hasSelectColumn.value[tableIdx] = false table.columns?.forEach((column, columnIdx) => { - acc[`tables.${tableIdx}.columns.${columnIdx}.column_name`] = [fieldRequiredValidator] - acc[`tables.${tableIdx}.columns.${columnIdx}.uidt`] = [fieldRequiredValidator] + acc[`tables.${tableIdx}.columns.${columnIdx}.column_name`] = [fieldRequiredValidator()] + acc[`tables.${tableIdx}.columns.${columnIdx}.uidt`] = [fieldRequiredValidator()] if (isSelect(column)) { hasSelectColumn.value[tableIdx] = true } diff --git a/packages/nc-gui/components/webhook/Editor.vue b/packages/nc-gui/components/webhook/Editor.vue index 9b9e2c7aec..99372b95e7 100644 --- a/packages/nc-gui/components/webhook/Editor.vue +++ b/packages/nc-gui/components/webhook/Editor.vue @@ -184,28 +184,28 @@ const methodList = ref([ const validators = computed(() => { return { - 'title': [fieldRequiredValidator], - 'eventOperation': [fieldRequiredValidator], - 'notification.type': [fieldRequiredValidator], + 'title': [fieldRequiredValidator()], + 'eventOperation': [fieldRequiredValidator()], + 'notification.type': [fieldRequiredValidator()], ...(hook.notification.type === 'URL' && { - 'notification.payload.method': [fieldRequiredValidator], - 'notification.payload.path': [fieldRequiredValidator], + 'notification.payload.method': [fieldRequiredValidator()], + 'notification.payload.path': [fieldRequiredValidator()], }), ...(hook.notification.type === 'Email' && { - 'notification.payload.to': [fieldRequiredValidator], - 'notification.payload.subject': [fieldRequiredValidator], - 'notification.payload.body': [fieldRequiredValidator], + 'notification.payload.to': [fieldRequiredValidator()], + 'notification.payload.subject': [fieldRequiredValidator()], + 'notification.payload.body': [fieldRequiredValidator()], }), ...((hook.notification.type === 'Slack' || hook.notification.type === 'Microsoft Teams' || hook.notification.type === 'Discord' || hook.notification.type === 'Mattermost') && { - 'notification.payload.channels': [fieldRequiredValidator], - 'notification.payload.body': [fieldRequiredValidator], + 'notification.payload.channels': [fieldRequiredValidator()], + 'notification.payload.body': [fieldRequiredValidator()], }), ...((hook.notification.type === 'Twilio' || hook.notification.type === 'Whatsapp Twilio') && { - 'notification.payload.body': [fieldRequiredValidator], - 'notification.payload.to': [fieldRequiredValidator], + 'notification.payload.body': [fieldRequiredValidator()], + 'notification.payload.to': [fieldRequiredValidator()], }), } }) diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index 04feb6edcd..8def53b7e9 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -616,7 +616,20 @@ "rowUpdateFailed": "Row update failed", "deleteRowFailed": "Failed to delete row", "setFormDataFailed": "Failed to set form data", - "formViewUpdateFailed": "Failed to update form view" + "formViewUpdateFailed": "Failed to update form view", + "tableNameRequired": "Table name is required", + "nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _", + "followingCharactersAreNotAllowed": "Following characters are not allowed", + "columnNameRequired": "Column name is required", + "projectNameExceeds50Characters": "Project name exceeds 50 characters", + "projectNameCannotStartWithSpace": "Project name cannot start with space", + "requiredField": "Required field", + "ipNotAllowed": "IP not allowed", + "targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type", + "theAcceptedFileTypeIsCsv": "The accepted file type is .csv", + "theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots", + "parameterKeyCannotBeEmpty": "Parameter key cannot be empty", + "duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed" }, "toast": { "exportMetadata": "Project metadata exported successfully", diff --git a/packages/nc-gui/pages/index/index/create-external.vue b/packages/nc-gui/pages/index/index/create-external.vue index 1a34237689..0568bf2d87 100644 --- a/packages/nc-gui/pages/index/index/create-external.vue +++ b/packages/nc-gui/pages/index/index/create-external.vue @@ -73,20 +73,20 @@ const validators = computed(() => { projectTitleValidator, ], 'extraParameters': [extraParameterValidator], - 'dataSource.client': [fieldRequiredValidator], + 'dataSource.client': [fieldRequiredValidator()], ...(formState.dataSource.client === ClientType.SQLITE ? { - 'dataSource.connection.connection.filename': [fieldRequiredValidator], + 'dataSource.connection.connection.filename': [fieldRequiredValidator()], } : { - 'dataSource.connection.host': [fieldRequiredValidator], - 'dataSource.connection.port': [fieldRequiredValidator], - 'dataSource.connection.user': [fieldRequiredValidator], - 'dataSource.connection.password': [fieldRequiredValidator], - 'dataSource.connection.database': [fieldRequiredValidator], + 'dataSource.connection.host': [fieldRequiredValidator()], + 'dataSource.connection.port': [fieldRequiredValidator()], + 'dataSource.connection.user': [fieldRequiredValidator()], + 'dataSource.connection.password': [fieldRequiredValidator()], + 'dataSource.connection.database': [fieldRequiredValidator()], ...([ClientType.PG, ClientType.MSSQL].includes(formState.dataSource.client) ? { - 'dataSource.searchPath.0': [fieldRequiredValidator], + 'dataSource.searchPath.0': [fieldRequiredValidator()], } : {}), }), diff --git a/packages/nc-gui/utils/validation.ts b/packages/nc-gui/utils/validation.ts index a5e63464a8..3b0a9b893b 100644 --- a/packages/nc-gui/utils/validation.ts +++ b/packages/nc-gui/utils/validation.ts @@ -1,9 +1,14 @@ +import { getI18n } from '~/plugins/a.i18n' + export const isEmail = (v: string) => /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(v) export function validateTableName(v: string, isGQL = false) { + const { t } = getI18n().global + if (!v) { - return 'Table name required' + // return 'Table name required' + return t('msg.error.tableNameRequired') } // GraphQL naming convention @@ -15,11 +20,13 @@ export function validateTableName(v: string, isGQL = false) { } if (/^[^_A-Za-z]/.test(v)) { - return 'Name should start with an alphabet or _' + // return 'Name should start with an alphabet or _' + return t('msg.error.nameShouldStartWithAnAlphabetOr_') } const m = v.match(/[^_A-Za-z\d]/g) if (m) { - return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + // return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + return `${t('msg.error.followingCharactersAreNotAllowed')} ${m.map((c) => JSON.stringify(c)).join(', ')}` } } else { // exclude . / \ @@ -27,7 +34,8 @@ export function validateTableName(v: string, isGQL = false) { // https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/acreldb/n0rfg6x1shw0ppn1cwhco6yn09f7.htm#:~:text=By%20default%2C%20MySQL%20encloses%20column,not%20truncate%20a%20longer%20name. const m = v.match(/[./\\]/g) if (m) { - return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + // return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + return `${t('msg.error.followingCharactersAreNotAllowed')} ${m.map((c) => JSON.stringify(c)).join(', ')}` } return true @@ -35,8 +43,10 @@ export function validateTableName(v: string, isGQL = false) { } export function validateColumnName(v: string, isGQL = false) { + const { t } = getI18n().global if (!v) { - return 'Column name required' + // return 'Column name required' + return t('msg.error.columnNameRequired') } // GraphQL naming convention @@ -47,11 +57,13 @@ export function validateColumnName(v: string, isGQL = false) { } if (/^[^_A-Za-z]/.test(v)) { - return 'Name should start with an alphabet or _' + // return 'Name should start with an alphabet or _' + return t('msg.error.nameShouldStartWithAnAlphabetOr_') } const m = v.match(/[^_A-Za-z\d]/g) if (m) { - return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + // return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + return `${t('msg.error.followingCharactersAreNotAllowed')} ${m.map((c) => JSON.stringify(c)).join(', ')}` } } else { // exclude . / \ @@ -59,7 +71,8 @@ export function validateColumnName(v: string, isGQL = false) { // https://documentation.sas.com/doc/en/pgmsascdc/9.4_3.5/acreldb/n0rfg6x1shw0ppn1cwhco6yn09f7.htm#:~:text=By%20default%2C%20MySQL%20encloses%20column,not%20truncate%20a%20longer%20name. const m = v.match(/[./\\]/g) if (m) { - return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + // return `Following characters are not allowed ${m.map((c) => JSON.stringify(c)).join(', ')}` + return `${t('msg.error.followingCharactersAreNotAllowed')} ${m.map((c) => JSON.stringify(c)).join(', ')}` } return true @@ -68,36 +81,40 @@ export function validateColumnName(v: string, isGQL = false) { export const projectTitleValidator = { validator: (rule: any, value: any, callback: (errMsg?: string) => void) => { + const { t } = getI18n().global if (value?.length > 50) { - callback('Project name exceeds 50 characters') + // callback('Project name exceeds 50 characters') + callback(t('msg.error.projectNameExceeds50Characters')) } if (value[0] === ' ') { - callback('Project name cannot start with space') + // callback('Project name cannot start with space') + callback(t('msg.error.projectNameCannotStartWithSpace')) } callback() }, } -export const fieldRequiredValidator = { - required: true, - message: 'Field is required', +export const fieldRequiredValidator = () => { + const { t } = getI18n().global + return { + required: true, + // message: `Required field`, + message: t('msg.error.requiredField'), + } } -export const getRequiredValidator = (field = 'Field') => ({ - required: true, - message: `${field} is required`, -}) - export const importUrlValidator = { validator: (rule: any, value: any) => { return new Promise((resolve, reject) => { + const { t } = getI18n().global if ( /(10)(\.([2]([0-5][0-5]|[01234][6-9])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}|(172)\.(1[6-9]|2[0-9]|3[0-1])(\.(2[0-4][0-9]|25[0-5]|[1][0-9][0-9]|[1-9][0-9]|[0-9])){2}|(192)\.(168)(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){2}|(0.0.0.0)|localhost?/g.test( value, ) ) { - return reject(new Error('IP Not allowed!')) + // return reject(new Error('IP Not allowed!')) + return reject(new Error(t('msg.error.ipNotAllowed'))) } return resolve(true) }) @@ -107,8 +124,12 @@ export const importUrlValidator = { export const importCsvUrlValidator = { validator: (rule: any, value: any) => { return new Promise((resolve, reject) => { + const { t } = getI18n().global if (value && !/.*\.(csv)/.test(value)) { - return reject(new Error('Target file is not an accepted file type. The accepted file type is .csv!')) + // return reject(new Error('Target file is not an accepted file type. The accepted file type is .csv!')) + return reject( + new Error(`${t('msg.error.targetFileIsNotAnAcceptedFileType')}. ${t('msg.error.theAcceptedFileTypeIsCsv')}`), + ) } return resolve(true) }) @@ -118,9 +139,13 @@ export const importCsvUrlValidator = { export const importExcelUrlValidator = { validator: (rule: any, value: any) => { return new Promise((resolve, reject) => { + const { t } = getI18n().global if (value && !/.*\.(xls|xlsx|xlsm|ods|ots)/.test(value)) { return reject( - new Error('Target file is not an accepted file type. The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots!'), + // new Error('Target file is not an accepted file type. The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots!'), + new Error( + `${t('msg.error.targetFileIsNotAnAcceptedFileType')}. ${t('msg.error.theAcceptedFileTypesAreXlsXlsxXlsmOdsOts')}`, + ), ) } return resolve(true) @@ -131,12 +156,15 @@ export const importExcelUrlValidator = { export const extraParameterValidator = { validator: (_: unknown, value: { key: string; value: string }[]) => { return new Promise((resolve, reject) => { + const { t } = getI18n().global for (const param of value) { if (param.key === '') { - return reject(new Error('Parameter key cannot be empty')) + // return reject(new Error('Parameter key cannot be empty')) + return reject(new Error(t('msg.error.parameterKeyCannotBeEmpty'))) } if (value.filter((el: any) => el.key === param.key).length !== 1) { - return reject(new Error('Duplicate parameter keys are not allowed')) + // return reject(new Error('Duplicate parameter keys are not allowed')) + return reject(new Error(t('msg.error.duplicateParameterKeysAreNotAllowed'))) } } return resolve(true)