diff --git a/lerna.json b/lerna.json index d21a1ffa..5186af2a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "1.2.12" + "version": "1.2.13" } diff --git a/packages/app/package.json b/packages/app/package.json index 0e1231af..f82cfffa 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@staticcms/app", - "version": "1.2.12", + "version": "1.2.13", "license": "MIT", "description": "Static CMS application.", "repository": "https://github.com/StaticJsCMS/static-cms", @@ -35,7 +35,7 @@ "@babel/eslint-parser": "7.21.3", "@babel/runtime": "7.21.0", "@emotion/babel-preset-css-prop": "11.10.0", - "@staticcms/core": "^1.2.12", + "@staticcms/core": "^1.2.13", "buffer": "6.0.3", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/packages/core/dev-test/config.yml b/packages/core/dev-test/config.yml index 6d699f28..5843ff71 100644 --- a/packages/core/dev-test/config.yml +++ b/packages/core/dev-test/config.yml @@ -141,7 +141,6 @@ collections: label: Pattern Validation widget: code pattern: ['.{12,}', 'Must have at least 12 characters'] - allow_input: true required: false - name: language label: Language Selection diff --git a/packages/core/package.json b/packages/core/package.json index 2e44865f..6a568914 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@staticcms/core", - "version": "1.2.12", + "version": "1.2.13", "license": "MIT", "description": "Static CMS core application.", "repository": "https://github.com/StaticJsCMS/static-cms", diff --git a/packages/core/src/actions/config.ts b/packages/core/src/actions/config.ts index 2a4093c8..0a9e8093 100644 --- a/packages/core/src/actions/config.ts +++ b/packages/core/src/actions/config.ts @@ -7,7 +7,12 @@ import yaml from 'yaml'; import { resolveBackend } from '../backend'; import { CONFIG_FAILURE, CONFIG_REQUEST, CONFIG_SUCCESS } from '../constants'; import validateConfig from '../constants/configSchema'; -import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n'; +import { + I18N, + I18N_FIELD_NONE, + I18N_FIELD_TRANSLATE, + I18N_STRUCTURE_SINGLE_FILE, +} from '../lib/i18n'; import { selectDefaultSortableFields } from '../lib/util/collection.util'; import type { AnyAction } from 'redux'; @@ -24,11 +29,13 @@ import type { } from '../interface'; import type { RootState } from '../store'; -function isObjectField(field: Field): field is ObjectField { +function isObjectField( + field: Field, +): field is ObjectField { return 'fields' in (field as ObjectField); } -function isFieldList(field: Field): field is ListField { +function isFieldList(field: Field): field is ListField { return 'types' in (field as ListField) || 'field' in (field as ListField); } @@ -70,9 +77,9 @@ function setDefaultPublicFolderForField(field: T) { function setI18nField(field: T) { if (field[I18N] === true) { - return { ...field, [I18N]: I18N_FIELD.TRANSLATE }; + return { ...field, [I18N]: I18N_FIELD_TRANSLATE }; } else if (field[I18N] === false || !field[I18N]) { - return { ...field, [I18N]: I18N_FIELD.NONE }; + return { ...field, [I18N]: I18N_FIELD_NONE }; } return field; } @@ -104,9 +111,9 @@ function setI18nDefaultsForFields(collectionOrFileFields: Field[], hasI18n: bool } function throwOnInvalidFileCollectionStructure(i18n?: I18nInfo) { - if (i18n && i18n.structure !== I18N_STRUCTURE.SINGLE_FILE) { + if (i18n && i18n.structure !== I18N_STRUCTURE_SINGLE_FILE) { throw new Error( - `i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`, + `i18n configuration for files collections is limited to ${I18N_STRUCTURE_SINGLE_FILE} structure`, ); } } diff --git a/packages/core/src/actions/entries.ts b/packages/core/src/actions/entries.ts index 27becb59..aafbe057 100644 --- a/packages/core/src/actions/entries.ts +++ b/packages/core/src/actions/entries.ts @@ -38,7 +38,13 @@ import { SORT_ENTRIES_SUCCESS, } from '../constants'; import ValidationErrorTypes from '../constants/validationErrorTypes'; -import { duplicateDefaultI18nFields, hasI18n, I18N_FIELD, serializeI18n } from '../lib/i18n'; +import { + duplicateDefaultI18nFields, + hasI18n, + I18N_FIELD_DUPLICATE, + I18N_FIELD_TRANSLATE, + serializeI18n, +} from '../lib/i18n'; import { serializeValues } from '../lib/serializeEntryValues'; import { Cursor } from '../lib/util'; import { selectFields, updateFieldByKey } from '../lib/util/collection.util'; @@ -899,7 +905,7 @@ function createEmptyDraftI18nData(collection: Collection, dataFields: Field[]) { } function skipField(field: Field) { - return field.i18n !== I18N_FIELD.DUPLICATE && field.i18n !== I18N_FIELD.TRANSLATE; + return field.i18n !== I18N_FIELD_DUPLICATE && field.i18n !== I18N_FIELD_TRANSLATE; } const i18nData = createEmptyDraftData(dataFields, skipField); diff --git a/packages/core/src/constants/configSchema.tsx b/packages/core/src/constants/configSchema.tsx index 06dddc17..0dd20990 100644 --- a/packages/core/src/constants/configSchema.tsx +++ b/packages/core/src/constants/configSchema.tsx @@ -8,7 +8,14 @@ import { v4 as uuid } from 'uuid'; import { formatExtensions, frontmatterFormats, extensionFormatters } from '../formats/formats'; import { getWidgets } from '../lib/registry'; -import { I18N_STRUCTURE, I18N_FIELD } from '../lib/i18n'; +import { + I18N_FIELD_DUPLICATE, + I18N_FIELD_NONE, + I18N_FIELD_TRANSLATE, + I18N_STRUCTURE_MULTIPLE_FILES, + I18N_STRUCTURE_MULTIPLE_FOLDERS, + I18N_STRUCTURE_SINGLE_FILE, +} from '../lib/i18n'; import type { ErrorObject } from 'ajv'; import type { Config } from '../interface'; @@ -18,7 +25,14 @@ const localeType = { type: 'string', minLength: 2, maxLength: 10, pattern: '^[a- const i18n = { type: 'object', properties: { - structure: { type: 'string', enum: Object.values(I18N_STRUCTURE) }, + structure: { + type: 'string', + enum: [ + I18N_STRUCTURE_MULTIPLE_FILES, + I18N_STRUCTURE_MULTIPLE_FOLDERS, + I18N_STRUCTURE_SINGLE_FILE, + ], + }, locales: { type: 'array', minItems: 2, @@ -39,7 +53,10 @@ const i18nCollection = { }; const i18nField = { - oneOf: [{ type: 'boolean' }, { type: 'string', enum: Object.values(I18N_FIELD) }], + oneOf: [ + { type: 'boolean' }, + { type: 'string', enum: [I18N_FIELD_DUPLICATE, I18N_FIELD_NONE, I18N_FIELD_TRANSLATE] }, + ], }; /** diff --git a/packages/core/src/interface.ts b/packages/core/src/interface.ts index 96dc5712..ae700d2e 100644 --- a/packages/core/src/interface.ts +++ b/packages/core/src/interface.ts @@ -16,7 +16,14 @@ import type { SORT_DIRECTION_NONE, } from './constants'; import type { formatExtensions } from './formats/formats'; -import type { I18N_STRUCTURE } from './lib/i18n'; +import type { + I18N_FIELD_DUPLICATE, + I18N_FIELD_NONE, + I18N_FIELD_TRANSLATE, + I18N_STRUCTURE_MULTIPLE_FILES, + I18N_STRUCTURE_MULTIPLE_FOLDERS, + I18N_STRUCTURE_SINGLE_FILE, +} from './lib/i18n'; import type { AllowedEvent } from './lib/registry'; import type Cursor from './lib/util/Cursor'; import type AssetProxy from './valueObjects/AssetProxy'; @@ -566,7 +573,7 @@ export interface CodeField extends BaseField { keys?: { code: string; lang: string }; output_code_only?: boolean; - code_mirror_config: { + code_mirror_config?: { extra_keys?: Record; } & Record; } @@ -689,11 +696,11 @@ export type Field = | ColorField | DateTimeField | FileOrImageField - | ListField + | ListField | MapField | MarkdownField | NumberField - | ObjectField + | ObjectField | RelationField | SelectField | HiddenField @@ -704,7 +711,7 @@ export interface ViewFilter { id?: string; label: string; field: string; - pattern: string; + pattern: string | boolean | number; } export interface ViewGroup { @@ -853,10 +860,20 @@ export interface EditorPersistOptions { duplicate?: boolean; } +export type I18nStructure = + | typeof I18N_STRUCTURE_MULTIPLE_FILES + | typeof I18N_STRUCTURE_MULTIPLE_FOLDERS + | typeof I18N_STRUCTURE_SINGLE_FILE; + +export type I18nField = + | typeof I18N_FIELD_DUPLICATE + | typeof I18N_FIELD_TRANSLATE + | typeof I18N_FIELD_NONE; + export interface I18nInfo { locales: string[]; defaultLocale: string; - structure?: I18N_STRUCTURE; + structure?: I18nStructure; } export interface ProcessedCodeLanguage { diff --git a/packages/core/src/lib/i18n.ts b/packages/core/src/lib/i18n.ts index f7b16d16..24eef87b 100644 --- a/packages/core/src/lib/i18n.ts +++ b/packages/core/src/lib/i18n.ts @@ -5,22 +5,26 @@ import groupBy from 'lodash/groupBy'; import { selectEntrySlug } from './util/collection.util'; import { set } from './util/object.util'; -import type { Field, Collection, Entry, EntryData, i18nCollection, I18nInfo } from '../interface'; +import type { + Field, + Collection, + Entry, + EntryData, + i18nCollection, + I18nInfo, + I18nStructure, +} from '../interface'; import type { EntryDraftState } from '../reducers/entryDraft'; export const I18N = 'i18n'; -export enum I18N_STRUCTURE { - MULTIPLE_FOLDERS = 'multiple_folders', - MULTIPLE_FILES = 'multiple_files', - SINGLE_FILE = 'single_file', -} +export const I18N_STRUCTURE_MULTIPLE_FOLDERS = 'multiple_folders'; +export const I18N_STRUCTURE_MULTIPLE_FILES = 'multiple_files'; +export const I18N_STRUCTURE_SINGLE_FILE = 'single_file'; -export enum I18N_FIELD { - TRANSLATE = 'translate', - DUPLICATE = 'duplicate', - NONE = 'none', -} +export const I18N_FIELD_TRANSLATE = 'translate'; +export const I18N_FIELD_DUPLICATE = 'duplicate'; +export const I18N_FIELD_NONE = 'none'; export function hasI18n(collection: Collection | i18nCollection): collection is i18nCollection { return I18N in collection; @@ -37,22 +41,22 @@ export function getI18nInfo(collection: Collection | i18nCollection): I18nInfo | export function getI18nFilesDepth(collection: Collection, depth: number) { const { structure } = getI18nInfo(collection) as I18nInfo; - if (structure === I18N_STRUCTURE.MULTIPLE_FOLDERS) { + if (structure === I18N_STRUCTURE_MULTIPLE_FOLDERS) { return depth + 1; } return depth; } export function isFieldTranslatable(field: Field, locale?: string, defaultLocale?: string) { - return locale !== defaultLocale && field.i18n === I18N_FIELD.TRANSLATE; + return locale !== defaultLocale && field.i18n === I18N_FIELD_TRANSLATE; } export function isFieldDuplicate(field: Field, locale?: string, defaultLocale?: string) { - return locale !== defaultLocale && field.i18n === I18N_FIELD.DUPLICATE; + return locale !== defaultLocale && field.i18n === I18N_FIELD_DUPLICATE; } export function isFieldHidden(field: Field, locale?: string, defaultLocale?: string) { - return locale !== defaultLocale && field.i18n === I18N_FIELD.NONE; + return locale !== defaultLocale && field.i18n === I18N_FIELD_NONE; } export function getLocaleDataPath(locale: string) { @@ -65,37 +69,37 @@ export function getDataPath(locale: string, defaultLocale: string) { } export function getFilePath( - structure: I18N_STRUCTURE, + structure: I18nStructure, extension: string, path: string, slug: string, locale: string, ) { switch (structure) { - case I18N_STRUCTURE.MULTIPLE_FOLDERS: + case I18N_STRUCTURE_MULTIPLE_FOLDERS: return path.replace(`/${slug}`, `/${locale}/${slug}`); - case I18N_STRUCTURE.MULTIPLE_FILES: + case I18N_STRUCTURE_MULTIPLE_FILES: return path.replace(new RegExp(`${escapeRegExp(extension)}$`), `${locale}.${extension}`); - case I18N_STRUCTURE.SINGLE_FILE: + case I18N_STRUCTURE_SINGLE_FILE: default: return path; } } -export function getLocaleFromPath(structure: I18N_STRUCTURE, extension: string, path: string) { +export function getLocaleFromPath(structure: I18nStructure, extension: string, path: string) { switch (structure) { - case I18N_STRUCTURE.MULTIPLE_FOLDERS: { + case I18N_STRUCTURE_MULTIPLE_FOLDERS: { const parts = path.split('/'); // filename parts.pop(); // locale return parts.pop(); } - case I18N_STRUCTURE.MULTIPLE_FILES: { + case I18N_STRUCTURE_MULTIPLE_FILES: { const parts = path.slice(0, -`.${extension}`.length); return parts.split('.').pop(); } - case I18N_STRUCTURE.SINGLE_FILE: + case I18N_STRUCTURE_SINGLE_FILE: default: return ''; } @@ -109,24 +113,24 @@ export function getFilePaths( ) { const { structure, locales } = getI18nInfo(collection) as I18nInfo; - if (structure === I18N_STRUCTURE.SINGLE_FILE) { + if (structure === I18N_STRUCTURE_SINGLE_FILE) { return [path]; } const paths = locales.map(locale => - getFilePath(structure as I18N_STRUCTURE, extension, path, slug, locale), + getFilePath(structure as I18nStructure, extension, path, slug, locale), ); return paths; } -export function normalizeFilePath(structure: I18N_STRUCTURE, path: string, locale: string) { +export function normalizeFilePath(structure: I18nStructure, path: string, locale: string) { switch (structure) { - case I18N_STRUCTURE.MULTIPLE_FOLDERS: + case I18N_STRUCTURE_MULTIPLE_FOLDERS: return path.replace(`${locale}/`, ''); - case I18N_STRUCTURE.MULTIPLE_FILES: + case I18N_STRUCTURE_MULTIPLE_FILES: return path.replace(`.${locale}`, ''); - case I18N_STRUCTURE.SINGLE_FILE: + case I18N_STRUCTURE_SINGLE_FILE: default: return path; } @@ -142,12 +146,12 @@ export function getI18nFiles( newPath?: string, ) { const { - structure = I18N_STRUCTURE.SINGLE_FILE, + structure = I18N_STRUCTURE_SINGLE_FILE, defaultLocale, locales, } = getI18nInfo(collection) as I18nInfo; - if (structure === I18N_STRUCTURE.SINGLE_FILE) { + if (structure === I18N_STRUCTURE_SINGLE_FILE) { const data = locales.reduce((map, locale) => { const dataPath = getDataPath(locale, defaultLocale); if (map) { @@ -230,7 +234,7 @@ export function formatI18nBackup( function mergeValues( collection: Collection, - structure: I18N_STRUCTURE, + structure: I18nStructure, defaultLocale: string, values: { locale: string; value: Entry }[], ) { @@ -285,13 +289,13 @@ export async function getI18nEntry( getEntryValue: (path: string) => Promise, ) { const { - structure = I18N_STRUCTURE.SINGLE_FILE, + structure = I18N_STRUCTURE_SINGLE_FILE, locales, defaultLocale, } = getI18nInfo(collection) as I18nInfo; let entryValue: Entry; - if (structure === I18N_STRUCTURE.SINGLE_FILE) { + if (structure === I18N_STRUCTURE_SINGLE_FILE) { entryValue = mergeSingleFileValue(await getEntryValue(path), defaultLocale, locales); } else { const entryValues = await Promise.all( @@ -315,11 +319,11 @@ export async function getI18nEntry( export function groupEntries(collection: Collection, extension: string, entries: Entry[]): Entry[] { const { - structure = I18N_STRUCTURE.SINGLE_FILE, + structure = I18N_STRUCTURE_SINGLE_FILE, defaultLocale, locales, } = getI18nInfo(collection) as I18nInfo; - if (structure === I18N_STRUCTURE.SINGLE_FILE) { + if (structure === I18N_STRUCTURE_SINGLE_FILE) { return entries.map(e => mergeSingleFileValue(e, defaultLocale, locales)); } @@ -349,7 +353,7 @@ export function getI18nDataFiles( diffFiles: { path: string; id: string; newFile: boolean }[], ) { const { structure } = getI18nInfo(collection) as I18nInfo; - if (structure === I18N_STRUCTURE.SINGLE_FILE) { + if (structure === I18N_STRUCTURE_SINGLE_FILE) { return diffFiles; } const paths = getFilePaths(collection, extension, path, slug); @@ -386,7 +390,7 @@ export function duplicateI18nFields( fieldPath: string, ) { const value = get(entryDraft, ['entry', 'data', ...fieldPath.split('.')]); - if (field.i18n === I18N_FIELD.DUPLICATE) { + if (field.i18n === I18N_FIELD_DUPLICATE) { locales .filter(l => l !== defaultLocale) .forEach(l => {