chore: dynamically loads date-fns locales

This commit is contained in:
James
2024-04-08 22:12:05 -04:00
parent 3e7925e33f
commit 69e884f5b7
55 changed files with 1667 additions and 1492 deletions

View File

@@ -33,23 +33,30 @@ export const RootLayout = async ({
const headers = getHeaders()
const cookies = parseCookies(headers)
const lang =
const languageCode =
getRequestLanguage({
config,
cookies,
headers,
}) ?? config.i18n.fallbackLanguage
const i18n = initI18n({ config: config.i18n, context: 'client', language: lang })
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
const clientConfig = await createClientConfig({ config, t: i18n.t })
const dir = rtlLanguages.includes(lang) ? 'RTL' : 'LTR'
const dir = rtlLanguages.includes(languageCode) ? 'RTL' : 'LTR'
const languageOptions = Object.entries(i18n.translations || {}).map(
([language, translations]) => ({
label: translations.general.thisLanguage,
const languageOptions = Object.entries(config.i18n.supportedLanguages || {}).reduce(
(acc, [language, languageConfig]) => {
if (Object.keys(config.i18n.supportedLanguages).includes(language)) {
acc.push({
label: languageConfig.translations.general.thisLanguage,
value: language,
}),
})
}
return acc
},
[],
)
// eslint-disable-next-line @typescript-eslint/require-await
@@ -71,17 +78,18 @@ export const RootLayout = async ({
})
return (
<html dir={dir} lang={lang}>
<html dir={dir} lang={languageCode}>
<body>
<RootProvider
componentMap={componentMap}
config={clientConfig}
dateFNSKey={i18n.dateFNSKey}
fallbackLang={clientConfig.i18n.fallbackLanguage}
lang={lang}
languageCode={languageCode}
languageOptions={languageOptions}
// eslint-disable-next-line react/jsx-no-bind
switchLanguageServerAction={switchLanguageServerAction}
translations={i18n.translations[lang]}
translations={i18n.translations}
>
{wrappedChildren}
</RootProvider>

View File

@@ -1,11 +1,5 @@
import type { BuildFormStateArgs } from '@payloadcms/ui/forms/buildStateFromSchema'
import type {
DocumentPreferences,
Field,
PayloadRequest,
SanitizedConfig,
TypeWithID,
} from 'payload/types'
import type { DocumentPreferences, Field, PayloadRequest, TypeWithID } from 'payload/types'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { reduceFieldsToValues } from '@payloadcms/ui/utilities/reduceFieldsToValues'
@@ -15,32 +9,21 @@ import type { FieldSchemaMap } from '../../utilities/buildFieldSchemaMap/types.j
import { buildFieldSchemaMap } from '../../utilities/buildFieldSchemaMap/index.js'
let cached: {
promise: Promise<FieldSchemaMap> | null
schemaMap: FieldSchemaMap | null
} = global._payload_fieldSchemaMap
let cached = global._payload_fieldSchemaMap
if (!cached) {
// eslint-disable-next-line no-multi-assign
cached = global._payload_fieldSchemaMap = { promise: null, schemaMap: null }
cached = global._payload_fieldSchemaMap = null
}
export const getFieldSchemaMap = async (config: SanitizedConfig): Promise<FieldSchemaMap> => {
if (cached.schemaMap && process.env.NODE_ENV !== 'development') {
return cached.schemaMap
export const getFieldSchemaMap = (req: PayloadRequest): FieldSchemaMap => {
if (cached && process.env.NODE_ENV !== 'development') {
return cached
}
if (!cached.promise) {
cached.promise = buildFieldSchemaMap(config)
}
cached = buildFieldSchemaMap(req)
try {
cached.schemaMap = await cached.promise
} catch (e) {
cached.promise = null
throw e
}
return cached.schemaMap
return cached
}
export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
@@ -76,7 +59,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
})
}
const fieldSchemaMap = await getFieldSchemaMap(req.payload.config)
const fieldSchemaMap = getFieldSchemaMap(req)
const id = collectionSlug ? reqData.id : undefined
const schemaPathSegments = schemaPath.split('.')

View File

@@ -1,37 +1,38 @@
import type { SanitizedConfig } from 'payload/types'
import type { PayloadRequest } from 'payload/types'
import type { FieldSchemaMap } from './types.js'
import { traverseFields } from './traverseFields.js'
export const buildFieldSchemaMap = async (config: SanitizedConfig): Promise<FieldSchemaMap> => {
export const buildFieldSchemaMap = ({
i18n,
payload: { config },
}: PayloadRequest): FieldSchemaMap => {
const result: FieldSchemaMap = new Map()
const validRelationships = config.collections.map((c) => c.slug) || []
await Promise.all(
config.collections.map(async (collection) => {
await traverseFields({
config.collections.forEach((collection) => {
traverseFields({
config,
fields: collection.fields,
i18n,
schemaMap: result,
schemaPath: collection.slug,
validRelationships,
})
}),
)
})
await Promise.all(
config.globals.map(async (global) => {
await traverseFields({
config.globals.forEach((global) => {
traverseFields({
config,
fields: global.fields,
i18n,
schemaMap: result,
schemaPath: global.slug,
validRelationships,
})
}),
)
})
return result
}

View File

@@ -1,3 +1,4 @@
import type { I18n } from '@payloadcms/translations'
import type { Field, SanitizedConfig } from 'payload/types'
import { tabHasName } from 'payload/types'
@@ -7,28 +8,30 @@ import type { FieldSchemaMap } from './types.js'
type Args = {
config: SanitizedConfig
fields: Field[]
i18n: I18n
schemaMap: FieldSchemaMap
schemaPath: string
validRelationships: string[]
}
export const traverseFields = async ({
export const traverseFields = ({
config,
fields,
i18n,
schemaMap,
schemaPath,
validRelationships,
}: Args): Promise<void> => {
await Promise.all(
fields.map(async (field) => {
}: Args) => {
fields.map((field) => {
switch (field.type) {
case 'group':
case 'array':
schemaMap.set(`${schemaPath}.${field.name}`, field.fields)
await traverseFields({
traverseFields({
config,
fields: field.fields,
i18n,
schemaMap,
schemaPath: `${schemaPath}.${field.name}`,
validRelationships,
@@ -37,9 +40,10 @@ export const traverseFields = async ({
case 'collapsible':
case 'row':
await traverseFields({
traverseFields({
config,
fields: field.fields,
i18n,
schemaMap,
schemaPath,
validRelationships,
@@ -47,27 +51,27 @@ export const traverseFields = async ({
break
case 'blocks':
await Promise.all(
field.blocks.map(async (block) => {
field.blocks.map((block) => {
const blockSchemaPath = `${schemaPath}.${field.name}.${block.slug}`
schemaMap.set(blockSchemaPath, block.fields)
await traverseFields({
traverseFields({
config,
fields: block.fields,
i18n,
schemaMap,
schemaPath: blockSchemaPath,
validRelationships,
})
}),
)
})
break
case 'richText':
if (typeof field.editor.generateSchemaMap === 'function') {
await field.editor.generateSchemaMap({
field.editor.generateSchemaMap({
config,
i18n,
schemaMap,
schemaPath: `${schemaPath}.${field.name}`,
})
@@ -76,25 +80,23 @@ export const traverseFields = async ({
break
case 'tabs':
await Promise.all(
field.tabs.map(async (tab) => {
field.tabs.map((tab) => {
const tabSchemaPath = tabHasName(tab) ? `${schemaPath}.${tab.name}` : schemaPath
if (tabHasName(tab)) {
schemaMap.set(tabSchemaPath, tab.fields)
}
await traverseFields({
traverseFields({
config,
fields: tab.fields,
i18n,
schemaMap,
schemaPath: tabSchemaPath,
validRelationships,
})
}),
)
})
break
}
}),
)
})
}

View File

@@ -71,7 +71,7 @@ export const createPayloadRequest = async ({
headers: request.headers,
})
const i18n = initI18n({
const i18n = await initI18n({
config: config.i18n,
context: 'api',
language,

View File

@@ -6,13 +6,13 @@ import { cookies, headers } from 'next/headers.js'
import { getRequestLanguage } from './getRequestLanguage.js'
export const getNextI18n = ({
export const getNextI18n = async ({
config,
language,
}: {
config: SanitizedConfig
language?: string
}): I18n =>
}): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',

View File

@@ -44,7 +44,7 @@ export const initPage = async ({
const cookies = parseCookies(headers)
const language = getRequestLanguage({ config: payload.config, cookies, headers })
const i18n = initI18n({
const i18n = await initI18n({
config: payload.config.i18n,
context: 'client',
language,

View File

@@ -89,7 +89,7 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
}
}
const i18n = getNextI18n({
const i18n = await getNextI18n({
config,
})

View File

@@ -19,7 +19,7 @@ export const generatePageMetadata = async ({
}): Promise<Metadata> => {
const config = await configPromise
const i18n = getNextI18n({
const i18n = await getNextI18n({
config,
})

View File

@@ -49,7 +49,7 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
const isGlobal = segmentOne === 'globals'
const isCollection = segmentOne === 'collections'
const i18n = getNextI18n({
const i18n = await getNextI18n({
config,
})

View File

@@ -85,7 +85,9 @@ export const SetStepNav: React.FC<{
url: `${adminRoute}/collections/${collectionSlug}/${id}/versions`,
},
{
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
label: doc?.createdAt
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
: '',
},
]
}
@@ -101,7 +103,9 @@ export const SetStepNav: React.FC<{
url: `${adminRoute}/globals/${globalConfig.slug}/versions`,
},
{
label: doc?.createdAt ? formatDate(doc.createdAt, dateFormat, i18n?.language) : '',
label: doc?.createdAt
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
: '',
},
]
}

View File

@@ -61,7 +61,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
} = config
const formattedCreatedAt = doc?.createdAt
? formatDate(doc.createdAt, dateFormat, i18n.language)
? formatDate({ date: doc.createdAt, i18n, pattern: dateFormat })
: ''
const originalDocFetchURL = `${serverURL}${apiRoute}/${globalSlug ? 'globals/' : ''}${

View File

@@ -88,7 +88,7 @@ export const SelectComparison: React.FC<Props> = (props) => {
setOptions((existingOptions) => [
...existingOptions,
...data.docs.map((doc) => ({
label: formatDate(doc.updatedAt, dateFormat, i18n.language),
label: formatDate({ date: doc.updatedAt, i18n, pattern: dateFormat }),
value: doc.id,
})),
])

View File

@@ -22,7 +22,7 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
const doc: any = {} // TODO: figure this out
const formattedCreatedAt = doc?.createdAt
? formatDate(doc.createdAt, config?.admin?.dateFormat, i18n?.language)
? formatDate({ date: doc.createdAt, i18n, pattern: config?.admin?.dateFormat })
: ''
if (collectionConfig) {

View File

@@ -38,7 +38,8 @@ export const CreatedAtCell: React.FC<CreatedAtCellProps> = ({
return (
<Link href={to}>
{cellData && formatDate(cellData as Date | number | string, dateFormat, i18n.language)}
{cellData &&
formatDate({ date: cellData as Date | number | string, i18n, pattern: dateFormat })}
</Link>
)
}

View File

@@ -34,9 +34,10 @@ type RichTextAdapterBase<
}) => Map<string, React.ReactNode>
generateSchemaMap?: (args: {
config: SanitizedConfig
i18n: I18n
schemaMap: Map<string, Field[]>
schemaPath: string
}) => Map<string, Field[]> | Promise<Map<string, Field[]>>
}) => Map<string, Field[]>
outputSchema?: ({
collectionIDFieldTypes,
config,

View File

@@ -1,4 +1,4 @@
import type { Translations } from '@payloadcms/translations'
import type { SupportedLanguages } from '@payloadcms/translations'
import type { Permissions } from '../../auth/index.js'
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
@@ -43,7 +43,7 @@ export type InitPageResult = {
locale: Locale
permissions: Permissions
req: PayloadRequest
translations: Translations
translations: SupportedLanguages
visibleEntities: VisibleEntities
}

View File

@@ -1,4 +1,4 @@
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import merge from 'deepmerge'
import type {

View File

@@ -673,11 +673,12 @@ export type Config = {
export type SanitizedConfig = Omit<
DeepRequired<Config>,
'collections' | 'endpoint' | 'globals' | 'localization'
'collections' | 'endpoint' | 'globals' | 'i18n' | 'localization'
> & {
collections: SanitizedCollectionConfig[]
endpoints: Endpoint[]
globals: SanitizedGlobalConfig[]
i18n: Required<I18nOptions>
localization: SanitizedLocalizationConfig | false
paths: {
config: string

View File

@@ -1,6 +1,6 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
@@ -8,7 +8,7 @@ import APIError from './APIError.js'
class AuthenticationError extends APIError {
constructor(t?: TFunction) {
super(
t ? t('error:emailOrPasswordIncorrect') : en.error.emailOrPasswordIncorrect,
t ? t('error:emailOrPasswordIncorrect') : en.translations.error.emailOrPasswordIncorrect,
httpStatus.UNAUTHORIZED,
)
}

View File

@@ -1,13 +1,16 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
class ErrorDeletingFile extends APIError {
constructor(t?: TFunction) {
super(t ? t('error:deletingFile') : en.error.deletingFile, httpStatus.INTERNAL_SERVER_ERROR)
super(
t ? t('error:deletingFile') : en.translations.error.deletingFile,
httpStatus.INTERNAL_SERVER_ERROR,
)
}
}

View File

@@ -1,6 +1,6 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
@@ -8,7 +8,7 @@ import APIError from './APIError.js'
class FileUploadError extends APIError {
constructor(t?: TFunction) {
super(
t ? t('error:problemUploadingFile') : en.error.problemUploadingFile,
t ? t('error:problemUploadingFile') : en.translations.error.problemUploadingFile,
httpStatus.BAD_REQUEST,
)
}

View File

@@ -1,6 +1,6 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
@@ -8,7 +8,7 @@ import APIError from './APIError.js'
class Forbidden extends APIError {
constructor(t?: TFunction) {
super(
t ? t('error:notAllowedToPerformAction') : en.error.notAllowedToPerformAction,
t ? t('error:notAllowedToPerformAction') : en.translations.error.notAllowedToPerformAction,
httpStatus.FORBIDDEN,
)
}

View File

@@ -1,13 +1,13 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
class LockedAuth extends APIError {
constructor(t?: TFunction) {
super(t ? t('error:userLocked') : en.error.userLocked, httpStatus.UNAUTHORIZED)
super(t ? t('error:userLocked') : en.translations.error.userLocked, httpStatus.UNAUTHORIZED)
}
}

View File

@@ -1,14 +1,16 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
class MissingFile extends APIError {
constructor(t?: TFunction) {
super(t ? t('error:noFilesUploaded') : en.error.noFilesUploaded, httpStatus.BAD_REQUEST)
super(
t ? t('error:noFilesUploaded') : en.translations.error.noFilesUploaded,
httpStatus.BAD_REQUEST,
)
}
}

View File

@@ -1,13 +1,13 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
class NotFound extends APIError {
constructor(t?: TFunction) {
super(t ? t('general:notFound') : en.general.notFound, httpStatus.NOT_FOUND)
super(t ? t('general:notFound') : en.translations.general.notFound, httpStatus.NOT_FOUND)
}
}

View File

@@ -1,13 +1,13 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
class UnauthorizedError extends APIError {
constructor(t?: TFunction) {
super(t ? t('error:unauthorized') : en.error.unauthorized, httpStatus.UNAUTHORIZED)
super(t ? t('error:unauthorized') : en.translations.error.unauthorized, httpStatus.UNAUTHORIZED)
}
}

View File

@@ -1,6 +1,6 @@
import type { TFunction } from '@payloadcms/translations'
import en from '@payloadcms/translations/languages/en'
import { en } from '@payloadcms/translations/languages/en'
import httpStatus from 'http-status'
import APIError from './APIError.js'
@@ -10,8 +10,8 @@ class ValidationError extends APIError<{ field: string; message: string }[]> {
const message = t
? t('error:followingFieldsInvalid', { count: results.length })
: results.length === 1
? en.error.followingFieldsInvalid_one
: en.error.followingFieldsInvalid_other
? en.translations.error.followingFieldsInvalid_one
: en.translations.error.followingFieldsInvalid_other
super(`${message} ${results.map((f) => f.field).join(', ')}`, httpStatus.BAD_REQUEST, results)
}

View File

@@ -2,8 +2,6 @@ import type { I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/config'
import type { FieldWithRichTextRequiredEditor } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import type { HTMLConverter } from '../converters/html/converter/types.js'
import type { FeatureProviderProviderServer } from '../types.js'
import type { ClientProps } from './feature.client.js'
@@ -66,9 +64,7 @@ export const LinkFeature: FeatureProviderProviderServer<LinkFeatureServerProps,
disabledCollections: props.disabledCollections,
enabledCollections: props.enabledCollections,
} as ExclusiveLinkCollectionsProps,
generateSchemaMap: ({ config, props }) => {
const i18n = initI18n({ config: config.i18n, context: 'client' })
generateSchemaMap: ({ config, i18n, props }) => {
return {
fields: transformExtraFields(
props.fields,

View File

@@ -1,4 +1,5 @@
import type { Transformer } from '@lexical/markdown'
import type { I18n } from '@payloadcms/translations'
import type { JSONSchema4 } from 'json-schema'
import type { Klass, LexicalEditor, LexicalNode, SerializedEditorState } from 'lexical'
import type { SerializedLexicalNode } from 'lexical'
@@ -178,6 +179,7 @@ export type ServerFeature<ServerProps, ClientFeatureProps> = {
clientFeatureProps?: ClientFeatureProps
generateComponentMap?: (args: {
config: SanitizedConfig
i18n: I18n
props: ServerProps
schemaPath: string
}) => {
@@ -185,12 +187,17 @@ export type ServerFeature<ServerProps, ClientFeatureProps> = {
}
generateSchemaMap?: (args: {
config: SanitizedConfig
i18n: I18n
props: ServerProps
schemaMap: Map<string, Field[]>
schemaPath: string
}) => {
}) =>
| {
[key: string]: Field[]
}
| Promise<{
[key: string]: Field[]
}>
generatedTypes?: {
modifyOutputSchema: ({
collectionIDFieldTypes,

View File

@@ -39,6 +39,7 @@ export const getGenerateComponentMap =
) {
const components = resolvedFeature.generateComponentMap({
config,
i18n,
props: resolvedFeature.serverFeatureProps,
schemaPath,
})
@@ -68,6 +69,7 @@ export const getGenerateComponentMap =
) {
const schemas = resolvedFeature.generateSchemaMap({
config,
i18n,
props: resolvedFeature.serverFeatureProps,
schemaMap: new Map(),
schemaPath,

View File

@@ -8,7 +8,7 @@ import { cloneDeep } from './field/lexical/utils/cloneDeep.js'
export const getGenerateSchemaMap =
(args: { resolvedFeatureMap: ResolvedServerFeatureMap }): RichTextAdapter['generateSchemaMap'] =>
({ config, schemaMap, schemaPath }) => {
({ config, i18n, schemaMap, schemaPath }) => {
const validRelationships = config.collections.map((c) => c.slug) || []
for (const [featureKey, resolvedFeature] of args.resolvedFeatureMap.entries()) {
@@ -20,6 +20,7 @@ export const getGenerateSchemaMap =
}
const schemas = resolvedFeature.generateSchemaMap({
config,
i18n,
props: resolvedFeature.serverFeatureProps,
schemaMap,
schemaPath,

View File

@@ -1,6 +1,5 @@
import type { RichTextAdapter } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { sanitizeFields } from 'payload/config'
import type { AdapterArguments, RichTextCustomElement } from './types.js'
@@ -12,8 +11,7 @@ import { uploadFieldsSchemaPath } from './field/elements/upload/shared.js'
export const getGenerateSchemaMap =
(args: AdapterArguments): RichTextAdapter['generateSchemaMap'] =>
({ config, schemaMap, schemaPath }) => {
const i18n = initI18n({ config: config.i18n, context: 'client' })
({ config, i18n, schemaMap, schemaPath }) => {
const validRelationships = config.collections.map((c) => c.slug) || []
;(args?.admin?.elements || Object.values(elementTypes)).forEach((el) => {

View File

@@ -56,6 +56,7 @@
"@payloadcms/eslint-config": "workspace:*",
"@swc/core": "^1.3.102",
"@types/react": "18.2.74",
"date-fns": "3.3.1",
"typescript": "5.4.4"
},
"files": [

View File

@@ -1,9 +1,11 @@
import ar from '../languages/ar.js'
import type { SupportedLanguages } from '../types.js'
import { ar } from '../languages/ar.js'
import az from '../languages/az.js'
import bg from '../languages/bg.js'
import cs from '../languages/cs.js'
import de from '../languages/de.js'
import en from '../languages/en.js'
import { de } from '../languages/de.js'
import { en } from '../languages/en.js'
import es from '../languages/es.js'
import fa from '../languages/fa.js'
import fr from '../languages/fr.js'
@@ -31,35 +33,33 @@ import zhTw from '../languages/zhTw.js'
export const translations = {
ar,
az,
bg,
cs,
// az,
// bg,
// cs,
de,
en,
es,
fa,
fr,
hr,
hu,
it,
ja,
ko,
my,
nb,
nl,
pl,
pt,
ro,
rs,
'rs-latin': rsLatin,
ru,
sv,
th,
tr,
ua,
vi,
zh,
'zh-tw': zhTw,
} as {
[locale: string]: Record<string, Record<string, string>>
}
// es,
// fa,
// fr,
// hr,
// hu,
// it,
// ja,
// ko,
// my,
// nb,
// nl,
// pl,
// pt,
// ro,
// rs,
// 'rs-latin': rsLatin,
// ru,
// sv,
// th,
// tr,
// ua,
// vi,
// zh,
// 'zh-tw': zhTw,
} as SupportedLanguages

View File

@@ -1,3 +1,4 @@
export { importDateFNSLocale } from '../importDateFNSLocale.js'
export { getTranslation } from '../utilities/getTranslation.js'
export { initI18n, matchLanguage, t } from '../utilities/init.js'
export type * from '../types.js'

View File

@@ -0,0 +1,116 @@
import type { Locale } from 'date-fns'
export const importDateFNSLocale = async (locale: string): Promise<Locale> => {
let result
switch (locale) {
case 'ar':
result = await import('date-fns/locale/ar')
break
case 'az':
result = await import('date-fns/locale/az')
break
case 'bg':
result = await import('date-fns/locale/bg')
break
case 'cs':
result = await import('date-fns/locale/cs')
break
case 'de':
result = await import('date-fns/locale/de')
break
case 'en-US':
result = await import('date-fns/locale/en-US')
break
case 'es':
result = await import('date-fns/locale/es')
break
case 'fa-IR':
result = await import('date-fns/locale/fa-IR')
break
case 'fr':
result = await import('date-fns/locale/fr')
break
case 'hr':
result = await import('date-fns/locale/hr')
break
case 'hu':
result = await import('date-fns/locale/hu')
break
case 'it':
result = await import('date-fns/locale/it')
break
case 'ja':
result = await import('date-fns/locale/ja')
break
case 'ko':
result = await import('date-fns/locale/ko')
break
case 'nb':
result = await import('date-fns/locale/nb')
break
case 'nl':
result = await import('date-fns/locale/nl')
break
case 'pl':
result = await import('date-fns/locale/pl')
break
case 'pt':
result = await import('date-fns/locale/pt')
break
case 'ro':
result = await import('date-fns/locale/ro')
break
case 'ru':
result = await import('date-fns/locale/ru')
break
case 'sv':
result = await import('date-fns/locale/sv')
break
case 'th':
result = await import('date-fns/locale/th')
break
case 'tr':
result = await import('date-fns/locale/tr')
break
case 'vi':
result = await import('date-fns/locale/vi')
break
case 'zh-CN':
result = await import('date-fns/locale/zh-CN')
break
case 'zh-TW':
result = await import('date-fns/locale/zh-TW')
break
}
if (result.default) return result.default
return result as Locale
}

View File

@@ -1,4 +1,6 @@
export default {
export const ar = {
dateFNSKey: 'ar',
translations: {
authentication: {
account: 'الحساب',
accountOfCurrentUser: 'حساب المستخدم الحالي',
@@ -110,7 +112,8 @@ export default {
block: 'وحدة محتوى',
blockType: 'نوع وحدة المحتوى',
blocks: 'وحدات المحتوى',
chooseBetweenCustomTextOrDocument: 'اختر بين إدخال عنوان URL نصّي مخصّص أو الرّبط بمستند آخر.',
chooseBetweenCustomTextOrDocument:
'اختر بين إدخال عنوان URL نصّي مخصّص أو الرّبط بمستند آخر.',
chooseDocumentToLink: 'اختر مستندًا للربط',
chooseFromExisting: 'اختر من القائمة',
chooseLabel: 'اختر {{label}}',
@@ -320,11 +323,13 @@ export default {
requiresAtLeast: 'هذا الحقل يتطلب على الأقل {{count}} {{label}}.',
requiresNoMoreThan: 'هذا الحقل يتطلب عدم تجاوز {{count}} {{label}}.',
requiresTwoNumbers: 'هذا الحقل يتطلب رقمين.',
shorterThanMax: 'يجب أن تكون هذه القيمة أقصر من الحد الأقصى للطول الذي هو {{maxLength}} أحرف.',
shorterThanMax:
'يجب أن تكون هذه القيمة أقصر من الحد الأقصى للطول الذي هو {{maxLength}} أحرف.',
trueOrFalse: 'يمكن أن يكون هذا الحقل مساويًا فقط للقيمتين صحيح أو خطأ.',
validUploadID: 'هذا الحقل ليس معرّف تحميل صالح.',
},
version: {
type: 'النّوع',
aboutToPublishSelection: 'أنت على وشك نشر كلّ {{label}} في التّحديد. هل أنت متأكّد؟',
aboutToRestore:
'أنت على وشك استرجاع هذا المستند {{label}} إلى الحالة التّي كان عليها في {{versionDate}}.',
@@ -365,7 +370,6 @@ export default {
showLocales: 'اظهر اللّغات:',
showingVersionsFor: 'يتمّ عرض النًّسخ ل:',
status: 'الحالة',
type: 'النّوع',
unpublish: 'الغاء النّشر',
unpublishing: 'يتمّ الغاء النّشر...',
version: 'النّسخة',
@@ -381,4 +385,5 @@ export default {
viewingVersions: 'يتمّ استعراض النُّسَخ ل {{entityLabel}} {{documentTitle}}',
viewingVersionsGlobal: 'يتمّ استعراض النُّسَخ للاعداد العامّ {{entityLabel}}',
},
},
}

View File

@@ -1,4 +1,6 @@
export default {
export const de = {
dateFNSKey: 'de',
translations: {
authentication: {
account: 'Konto',
accountOfCurrentUser: 'Aktuelles Benutzerkonto',
@@ -322,7 +324,8 @@ export default {
invalidSelections: "'Dieses Feld enthält die folgenden inkorrekten Auswahlen:'",
lessThanMin: '{{value}} ist kleiner als der minimal erlaubte {{label}} von {{min}}.',
limitReached: 'Limit erreicht, es können nur {{max}} Elemente hinzugefügt werden.',
longerThanMin: 'Dieser Wert muss länger als die minimale Länge von {{minLength}} Zeichen sein.',
longerThanMin:
'Dieser Wert muss länger als die minimale Länge von {{minLength}} Zeichen sein.',
notValidDate: '"{{value}}" ist kein gültiges Datum.',
required: 'Pflichtfeld',
requiresAtLeast: 'Dieses Feld muss mindestens {{count}} {{label}} enthalten.',
@@ -333,6 +336,7 @@ export default {
validUploadID: "'Dieses Feld enthält keine valide Upload-ID.'",
},
version: {
type: 'Typ',
aboutToPublishSelection:
'Sie sind dabei, alle {{label}} in der Auswahl zu veröffentlichen. Bist du dir sicher?',
aboutToRestore: 'Du bist dabei, {{label}} auf den Stand vom {{versionDate}} zurücksetzen.',
@@ -375,7 +379,6 @@ export default {
showLocales: 'Sprachumgebungen anzeigen:',
showingVersionsFor: 'Versionen anzeigen für:',
status: 'Status',
type: 'Typ',
unpublish: 'Auf Entwurf setzen',
unpublishing: 'Setze auf Entwurf...',
version: 'Version',
@@ -391,4 +394,5 @@ export default {
viewingVersions: 'Betrachte Versionen für {{entityLabel}} {{documentTitle}}',
viewingVersionsGlobal: '`Betrachte Versionen für das Globale Dokument {{entityLabel}}',
},
},
}

View File

@@ -1,4 +1,6 @@
export default {
export const en = {
dateFNSKey: 'en-US',
translations: {
authentication: {
account: 'Account',
accountOfCurrentUser: 'Account of current user',
@@ -175,10 +177,10 @@ export default {
create: 'Create',
createNew: 'Create New',
createNewLabel: 'Create new {{label}}',
creatingNewLabel: 'Creating new {{label}}',
created: 'Created',
createdAt: 'Created At',
creating: 'Creating',
creatingNewLabel: 'Creating new {{label}}',
dark: 'Dark',
dashboard: 'Dashboard',
delete: 'Delete',
@@ -228,8 +230,8 @@ export default {
notFound: 'Not Found',
nothingFound: 'Nothing found',
of: 'of',
or: 'Or',
open: 'Open',
or: 'Or',
order: 'Order',
pageNotFound: 'Page not found',
password: 'Password',
@@ -291,11 +293,11 @@ export default {
dragAndDrop: 'Drag and drop a file',
dragAndDropHere: 'or drag and drop a file here',
editImage: 'Edit Image',
fileName: 'File Name',
fileSize: 'File Size',
focalPoint: 'Focal Point',
focalPointDescription:
'Drag the focal point directly on the preview or adjust the values below.',
fileName: 'File Name',
fileSize: 'File Size',
height: 'Height',
lessInfo: 'Less info',
moreInfo: 'More info',
@@ -318,7 +320,8 @@ export default {
invalidSelections: 'This field has the following invalid selections:',
lessThanMin: '{{value}} is less than the min allowed {{label}} of {{min}}.',
limitReached: 'Limit reached, only {{max}} items can be added.',
longerThanMin: 'This value must be longer than the minimum length of {{minLength}} characters.',
longerThanMin:
'This value must be longer than the minimum length of {{minLength}} characters.',
notValidDate: '"{{value}}" is not a valid date.',
required: 'This field is required.',
requiresAtLeast: 'This field requires at least {{count}} {{label}}.',
@@ -329,6 +332,7 @@ export default {
validUploadID: 'This field is not a valid upload ID.',
},
version: {
type: 'Type',
aboutToPublishSelection:
'You are about to publish all {{label}} in the selection. Are you sure?',
aboutToRestore:
@@ -372,7 +376,6 @@ export default {
showLocales: 'Show locales:',
showingVersionsFor: 'Showing versions for:',
status: 'Status',
type: 'Type',
unpublish: 'Unpublish',
unpublishing: 'Unpublishing...',
version: 'Version',
@@ -388,4 +391,5 @@ export default {
viewingVersions: 'Viewing versions for the {{entityLabel}} {{documentTitle}}',
viewingVersionsGlobal: 'Viewing versions for the global {{entityLabel}}',
},
},
}

View File

@@ -1,48 +1,56 @@
export type LanguageTranslations = {
import type { Locale } from 'date-fns'
import type { acceptedLanguages } from './utilities/init.js'
export type Language = {
dateFNSKey: string
translations: {
[namespace: string]: {
[key: string]: string
}
}
}
export type Translations = {
[language: string]: LanguageTranslations
export type AcceptedLanguages = (typeof acceptedLanguages)[number]
export type SupportedLanguages = {
[key in AcceptedLanguages]?: Language
}
export type TFunction = (key: string, options?: Record<string, any>) => string
export type I18n = {
dateFNS: Locale
/** Corresponding dateFNS key */
dateFNSKey: string
/** The fallback language */
fallbackLanguage: string
/** The language of the request */
language: string
/** Translate function */
t: TFunction
translations: Translations
translations: Language['translations']
}
export type I18nOptions = {
fallbackLanguage?: string
supportedLanguages?: Translations
translations?: {
[language: string]:
| {
$schema: string
}
| LanguageTranslations
}
supportedLanguages?: SupportedLanguages
translations?: Partial<{
[key in AcceptedLanguages]?: Language['translations']
}>
}
export type InitTFunction = (args: {
config: I18nOptions
language?: string
translations: Translations
translations: Language['translations']
}) => {
t: TFunction
translations: Translations
translations: Language['translations']
}
export type InitI18n = (args: {
config: I18nOptions
context: 'api' | 'client'
language?: string
}) => I18n
}) => Promise<I18n>

View File

@@ -1,4 +1,4 @@
import type { Translations } from '../types.js'
import type { Language } from '../types.js'
import { clientTranslationKeys } from '../clientKeys.js'
@@ -54,18 +54,10 @@ function sortObject(obj) {
return sortedObject
}
export const reduceLanguages = (supportedLanguages: Translations, context: 'api' | 'client') => {
const languages = {}
Object.entries(supportedLanguages).forEach(([lang, translations]) => {
export const getTranslationsByContext = (translations: Language, context: 'api' | 'client') => {
if (context === 'client') {
const clientTranslations = sortObject(filterKeys(translations, '', clientTranslationKeys))
languages[lang] = clientTranslations
return sortObject(filterKeys(translations, '', clientTranslationKeys))
} else {
languages[lang] = translations
return translations
}
})
return languages
}

View File

@@ -1,7 +1,8 @@
import type { I18n, InitI18n, InitTFunction, Translations } from '../types.js'
import type { I18n, InitI18n, InitTFunction, Language } from '../types.js'
import { importDateFNSLocale } from '../importDateFNSLocale.js'
import { deepMerge } from './deepMerge.js'
import { reduceLanguages } from './reduceLanguages.js'
import { getTranslationsByContext } from './getTranslationsByContext.js'
/**
* @function getTranslationString
@@ -17,7 +18,7 @@ export const getTranslationString = ({
}: {
count?: number
key: string
translations: Translations[0]
translations: Language['translations']
}) => {
const keys = key.split(':')
let keySuffix = ''
@@ -104,7 +105,7 @@ type TFunctionConstructor = ({
vars,
}: {
key: string
translations?: Translations[0]
translations?: Language['translations']
vars?: Record<string, any>
}) => string
@@ -178,7 +179,7 @@ export const acceptedLanguages = [
'vi',
'zh',
'zhTw',
]
] as const
export function matchLanguage(header: string): string | undefined {
const parsedHeader = parseAcceptLanguage(header)
@@ -196,14 +197,13 @@ export function matchLanguage(header: string): string | undefined {
const initTFunction: InitTFunction = (args) => {
const { config, language, translations } = args
const mergedTranslations = deepMerge(config?.translations ?? {}, translations)
const languagePreference = matchLanguage(language)
const mergedTranslations = deepMerge(config?.translations?.[language] ?? {}, translations)
return {
t: (key, vars) => {
return t({
key,
translations: mergedTranslations[languagePreference],
translations: mergedTranslations,
vars,
})
},
@@ -211,14 +211,14 @@ const initTFunction: InitTFunction = (args) => {
}
}
function memoize(fn: (args: unknown) => I18n, keys: string[]) {
function memoize(fn: (args: unknown) => Promise<I18n>, keys: string[]) {
const cacheMap = new Map()
const memoized = (args) => {
const memoized = async (args) => {
const cacheKey = keys.reduce((acc, key) => acc + args[key], '')
if (!cacheMap.has(cacheKey)) {
const result = fn(args)
const result = await fn(args)
cacheMap.set(cacheKey, result)
}
@@ -229,20 +229,29 @@ function memoize(fn: (args: unknown) => I18n, keys: string[]) {
}
export const initI18n: InitI18n = memoize(
({ config, context, language = 'en' }: Parameters<InitI18n>[0]) => {
const languages = reduceLanguages(config.supportedLanguages, context)
async ({ config, context, language = 'en' }: Parameters<InitI18n>[0]) => {
const translations = getTranslationsByContext(
config.supportedLanguages[language].translations,
context,
)
const { t, translations } = initTFunction({
const { t, translations: mergedTranslations } = initTFunction({
config,
language: language || config.fallbackLanguage,
translations: languages,
translations,
})
const dateFNSKey = config.supportedLanguages[language]?.dateFNSKey || 'en-US'
const dateFNS = await importDateFNSLocale(dateFNSKey)
const i18n: I18n = {
dateFNS,
dateFNSKey,
fallbackLanguage: config.fallbackLanguage,
language: language || config.fallbackLanguage,
t,
translations,
translations: mergedTranslations,
}
return i18n

View File

@@ -131,7 +131,7 @@ export const Autosave: React.FC<Props> = ({
{!saving && lastSaved && (
<React.Fragment>
{t('version:lastSavedAgo', {
distance: formatTimeToNow(lastSaved, i18n.language),
distance: formatTimeToNow({ date: lastSaved, i18n }),
})}
</React.Fragment>
)}

View File

@@ -11,7 +11,7 @@ import type { Props } from './types.js'
import { Calendar as CalendarIcon } from '../../icons/Calendar/index.js'
import { X as XIcon } from '../../icons/X/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { getDateLocale } from '../../utilities/getDateLocale.js'
import { getFormattedLocale } from './getFormattedLocale.js'
import './index.scss'
const baseClass = 'date-time-picker'
@@ -37,10 +37,10 @@ const DateTime: React.FC<Props> = (props) => {
// Use the user's AdminUI language preference for the locale
const { i18n } = useTranslation()
const { locale, localeData } = getDateLocale(i18n.language)
const datepickerLocale = getFormattedLocale(i18n.language)
try {
registerLocale(locale, localeData)
registerLocale(datepickerLocale, i18n.dateFNS)
} catch (e) {
console.warn(`Could not find DatePicker locale for ${i18n.language}`)
}
@@ -108,7 +108,7 @@ const DateTime: React.FC<Props> = (props) => {
<ReactDatePicker
{...dateTimePickerProps}
dropdownMode="select"
locale={locale}
locale={datepickerLocale}
showMonthDropdown
showYearDropdown
/>

View File

@@ -0,0 +1,12 @@
export const getFormattedLocale = (language = 'enUS') => {
const formattedLocales = {
en: 'enUS',
my: 'enUS', // Burmese is not currently supported
ua: 'uk',
zh: 'zhCN',
}
const formattedLocale = formattedLocales[language] || language
return formattedLocale
}

View File

@@ -122,13 +122,15 @@ export const DocumentControls: React.FC<{
.filter(Boolean)
.join(' ')}
title={
data?.updatedAt ? formatDate(data?.updatedAt, dateFormat, i18n.language) : ''
data?.updatedAt
? formatDate({ date: data?.updatedAt, i18n, pattern: dateFormat })
: ''
}
>
<p className={`${baseClass}__label`}>{i18n.t('general:lastModified')}:&nbsp;</p>
{data?.updatedAt && (
<p className={`${baseClass}__value`}>
{formatDate(data.updatedAt, dateFormat, i18n.language)}
{formatDate({ date: data?.updatedAt, i18n, pattern: dateFormat })}
</p>
)}
</li>
@@ -137,13 +139,15 @@ export const DocumentControls: React.FC<{
.filter(Boolean)
.join(' ')}
title={
data?.createdAt ? formatDate(data?.createdAt, dateFormat, i18n.language) : ''
data?.createdAt
? formatDate({ date: data?.createdAt, i18n, pattern: dateFormat })
: ''
}
>
<p className={`${baseClass}__label`}>{i18n.t('general:created')}:&nbsp;</p>
{data?.createdAt && (
<p className={`${baseClass}__value`}>
{formatDate(data?.createdAt, dateFormat, i18n.language)}
{formatDate({ date: data?.createdAt, i18n, pattern: dateFormat })}
</p>
)}
</li>

View File

@@ -18,5 +18,5 @@ export const DateCell: React.FC<DefaultCellComponentProps<Date | number | string
const dateFormat = dateDisplayFormat || dateFormatFromConfig
return <span>{cellData && formatDate(cellData, dateFormat, i18n.language)}</span>
return <span>{cellData && formatDate({ date: cellData, i18n, pattern: dateFormat })}</span>
}

View File

@@ -1,5 +1,5 @@
'use client'
import type { LanguageTranslations } from '@payloadcms/translations'
import type { Language } from '@payloadcms/translations'
import type { ClientConfig } from 'payload/types'
import * as facelessUIImport from '@faceless-ui/modal'
@@ -33,19 +33,21 @@ type Props = {
children: React.ReactNode
componentMap: ComponentMap
config: ClientConfig
dateFNSKey: Language['dateFNSKey']
fallbackLang: ClientConfig['i18n']['fallbackLanguage']
lang: string
languageCode: string
languageOptions: LanguageOptions
switchLanguageServerAction?: (lang: string) => Promise<void>
translations: LanguageTranslations
translations: Language['translations']
}
export const RootProvider: React.FC<Props> = ({
children,
componentMap,
config,
dateFNSKey,
fallbackLang,
lang,
languageCode,
languageOptions,
switchLanguageServerAction,
translations,
@@ -65,8 +67,9 @@ export const RootProvider: React.FC<Props> = ({
<FieldComponentsProvider>
<ClientFunctionProvider>
<TranslationProvider
dateFNSKey={dateFNSKey}
fallbackLang={fallbackLang}
lang={lang}
language={languageCode}
languageOptions={languageOptions}
switchLanguageServerAction={switchLanguageServerAction}
translations={translations}

View File

@@ -1,9 +1,12 @@
'use client'
import type { I18n, LanguageTranslations } from '@payloadcms/translations'
import type { I18n, Language } from '@payloadcms/translations'
import type { Locale } from 'date-fns'
import type { ClientConfig } from 'payload/types'
import { t } from '@payloadcms/translations'
import React, { createContext, useContext } from 'react'
import { importDateFNSLocale } from '@payloadcms/translations'
import enUS from 'date-fns/locale/en-US'
import React, { createContext, useContext, useEffect, useState } from 'react'
import { useRouteCache } from '../RouteCache/index.js'
@@ -19,6 +22,8 @@ const Context = createContext<{
t: (key: string, vars?: Record<string, any>) => string
}>({
i18n: {
dateFNS: enUS,
dateFNSKey: 'en-US',
fallbackLanguage: 'en',
language: 'en',
t: (key) => key,
@@ -31,22 +36,25 @@ const Context = createContext<{
type Props = {
children: React.ReactNode
dateFNSKey: Language['dateFNSKey']
fallbackLang: ClientConfig['i18n']['fallbackLanguage']
lang: string
language: string
languageOptions: LanguageOptions
switchLanguageServerAction: (lang: string) => Promise<void>
translations: LanguageTranslations
translations: Language['translations']
}
export const TranslationProvider: React.FC<Props> = ({
children,
dateFNSKey,
fallbackLang,
lang,
language,
languageOptions,
switchLanguageServerAction,
translations,
}) => {
const { clearRouteCache } = useRouteCache()
const [dateFNS, setDateFNS] = useState<Locale>()
const nextT = (key: string, vars?: Record<string, unknown>): string =>
t({
@@ -68,16 +76,26 @@ export const TranslationProvider: React.FC<Props> = ({
[switchLanguageServerAction, clearRouteCache],
)
useEffect(() => {
const loadDateFNS = async () => {
const imported = await importDateFNSLocale(dateFNSKey)
setDateFNS(imported)
}
void loadDateFNS()
}, [dateFNSKey])
return (
<Context.Provider
value={{
i18n: {
dateFNS,
dateFNSKey,
fallbackLanguage: fallbackLang,
language: lang,
language,
t: nextT,
translations: {
[lang]: translations,
},
translations,
},
languageOptions,
switchLanguage,

View File

@@ -1,22 +1,24 @@
import type { I18n } from '@payloadcms/translations'
import { format, formatDistanceToNow } from 'date-fns'
import { getDateLocale } from './getDateLocale.js'
export const formatDate = (
date: Date | number | string | undefined,
pattern: string,
locale?: string,
): string => {
const theDate = new Date(date)
const { localeData } = getDateLocale(locale)
return format(theDate, pattern, { locale: localeData })
type FormatDateArgs = {
date: Date | number | string | undefined
i18n: I18n
pattern: string
}
export const formatTimeToNow = (
date: Date | number | string | undefined,
locale?: string,
): string => {
export const formatDate = ({ date, i18n, pattern }: FormatDateArgs): string => {
const theDate = new Date(date)
const { localeData } = getDateLocale(locale)
return formatDistanceToNow(theDate, { locale: localeData })
return format(theDate, pattern, { locale: i18n.dateFNS })
}
type FormatTimeToNowArgs = {
date: Date | number | string | undefined
i18n: I18n
}
export const formatTimeToNow = ({ date, i18n }: FormatTimeToNowArgs): string => {
const theDate = new Date(date)
return formatDistanceToNow(theDate, { locale: i18n.dateFNS })
}

View File

@@ -44,7 +44,7 @@ export const formatDocTitle = ({
const dateFormat =
('date' in fieldConfig.admin && fieldConfig?.admin?.date?.displayFormat) ||
dateFormatFromConfig
title = formatDate(title, dateFormat, i18n.language) || title
title = formatDate({ date: title, i18n, pattern: dateFormat }) || title
}
}
}

View File

@@ -1,24 +0,0 @@
import type { Locale } from 'date-fns'
import { dateLocales } from './dateLocales.js'
export const getDateLocale = (
locale = 'enUS',
): {
locale: string
localeData: Locale
} => {
const formattedLocales = {
en: 'enUS',
my: 'enUS', // Burmese is not currently supported
ua: 'uk',
zh: 'zhCN',
}
const formattedLocale = formattedLocales[locale] || locale
return {
locale: formattedLocale,
localeData: dateLocales[formattedLocale],
}
}

4
pnpm-lock.yaml generated
View File

@@ -1345,6 +1345,9 @@ importers:
'@types/react':
specifier: 18.2.74
version: 18.2.74
date-fns:
specifier: 3.3.1
version: 3.3.1
typescript:
specifier: 5.4.4
version: 5.4.4
@@ -8358,7 +8361,6 @@ packages:
/date-fns@3.3.1:
resolution: {integrity: sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==}
dev: false
/dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}

View File

@@ -25,6 +25,8 @@ import {
UploadFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
import { de } from '@payloadcms/translations/languages/de'
import { en } from '@payloadcms/translations/languages/en'
// import { slateEditor } from '@payloadcms/richtext-slate'
import { type Config, buildConfig } from 'payload/config'
import sharp from 'sharp'
@@ -171,6 +173,12 @@ export async function buildConfigWithDefaults(
}),
sharp,
telemetry: false,
i18n: {
supportedLanguages: {
en,
de,
},
},
typescript: {
declare: false,
},