Compare commits

..

49 Commits

Author SHA1 Message Date
Elliot DeNolf
e9c7f9da92 chore(release): v3.0.0-alpha.61 [skip ci] 2024-04-09 14:22:12 -04:00
James Mikrut
0ce26d2c08 Feat/config i18n (#5735) 2024-04-09 14:13:17 -04:00
Alessio Gravili
abf285d713 chore: get dev:generate-types to work again (#5750) 2024-04-09 14:12:23 -04:00
James
c2ee8e3999 chore: de-flakes fields/index tests 2024-04-09 13:56:32 -04:00
James
167ba0c68f chore: adds back all tests 2024-04-09 13:50:54 -04:00
James
9ad1cbe920 chore: adjusts test snapshot logic to restore uploads properly 2024-04-09 13:33:42 -04:00
James
313ea52e3d chore: getRequestLanguage now defaults 2024-04-09 13:17:43 -04:00
James
3acfb7a83f chore: corrects imports 2024-04-09 12:43:36 -04:00
James
783dae2bbb Merge branch 'alpha' of github.com:payloadcms/payload into feat/config-i18n 2024-04-09 12:35:57 -04:00
Alessio Gravili
59681b211b fix(richtext-lexical): upload nodes weren't visible (#5746) 2024-04-09 12:32:24 -04:00
James
98438175cf chore: handles server errors 2024-04-09 12:31:25 -04:00
Alessio Gravili
af40302e5f fix(richtext-lexical): get links to work again (#5745) 2024-04-09 12:29:45 -04:00
Alessio Gravili
ec0e0ae449 chore(richtext-lexical): add e2e test to ensure that pre-seeded upload nodes are visible 2024-04-09 12:24:54 -04:00
Alessio Gravili
607ff17033 fix(richtext-lexical): upload nodes weren't visible due to incorrect relationships condition 2024-04-09 12:24:30 -04:00
Alessio Gravili
e73e610669 chore: fields test suite: clearAndSeedEverything instead of seed for dev as well, to ensure same state as test runs (most importantly, this gets rid of leftover uploads) 2024-04-09 12:22:42 -04:00
Jarrod Flesch
30fddde066 chore: corrects type for getLocalI18n 2024-04-09 11:51:40 -04:00
Jarrod Flesch
a56d2842fb chore: converts ua to uk 2024-04-09 11:44:34 -04:00
Jarrod Flesch
35f59a47cc chore: corrects dateFNS keys, stricter types 2024-04-09 11:38:38 -04:00
Jarrod Flesch
817d57bd12 chore: migrate langs 2024-04-09 11:05:35 -04:00
Elliot DeNolf
5826048e7b ci: publish script throttling 2024-04-09 09:50:23 -04:00
Elliot DeNolf
1a975b31cf chore(release): v3.0.0-alpha.60 [skip ci] 2024-04-09 09:36:41 -04:00
James
73298a80f0 chore: adds more de-flake to tabs 2024-04-09 09:34:11 -04:00
James
a5d14ef4c1 chore: de-flakes tabs 2024-04-09 09:34:11 -04:00
James
d0c79b65f8 chore: skips index to see what remains 2024-04-09 09:34:11 -04:00
James
2fc50b1a1f chore: de-flakes array 2024-04-09 09:34:11 -04:00
James
0ddeedb0b3 chore: de-flakes relationship suite 2024-04-09 09:34:11 -04:00
James
09c2fb10f3 chore: bug in ci 2024-04-09 09:34:11 -04:00
James
5bfff5b7ba chore: ensures artifacts work 2024-04-09 09:34:11 -04:00
James
702088375c chore: attempts to de-flake 2024-04-09 09:34:11 -04:00
James
ea507fbcc4 chore: moves lexical tests into collection folder 2024-04-09 09:34:11 -04:00
James
0ff1e6632b chore: splits out relationship 2024-04-09 09:34:11 -04:00
James
996ee47f96 chore: splits out blocks and array into their own suites 2024-04-09 09:34:11 -04:00
James
3e9bd5bb62 chore: uses ci env var to set max retries 2024-04-09 09:34:11 -04:00
James
ee7221c986 chore: sets maxRetries 2024-04-09 09:34:11 -04:00
James
318c126ae3 chore: turns off prebuild for fields 2024-04-09 09:34:11 -04:00
Jarrod Flesch
2154aea89f chore: comment out all other test suites for now 2024-04-09 09:34:11 -04:00
Jarrod Flesch
4d3ad1af35 chore: run fields e2e on ci 2024-04-09 09:34:11 -04:00
Alessio Gravili
75cab7688f chore: fix incorrect next tsconfig paths breaking monorepo setup (#5743) 2024-04-09 09:26:09 -04:00
James
5084d6dd97 chore: attempts to de-flake live preview 2024-04-08 22:34:23 -04:00
James
518f80cbb6 chore: es 2024-04-08 22:29:41 -04:00
James
c9399efa65 chore: fixes access control test 2024-04-08 22:25:35 -04:00
James
d3016b7eb5 chore: merge 2024-04-08 22:13:16 -04:00
James
69e884f5b7 chore: dynamically loads date-fns locales 2024-04-08 22:12:05 -04:00
James
3e7925e33f chore: removes async nature from a few lexical things 2024-04-08 16:48:56 -04:00
James
7a2ccba63c Merge branch 'feat/config-i18n' of github.com:payloadcms/payload into feat/config-i18n 2024-04-08 16:39:03 -04:00
James
be2134eb69 chore: requires languages to be passed to config 2024-04-08 16:38:12 -04:00
James Mikrut
95e422b0e1 Merge branch 'alpha' into feat/config-i18n 2024-04-08 16:26:38 -04:00
James
53d9c4ca95 chore: dynamically loads translations 2024-04-08 16:25:24 -04:00
James
30948ab545 chore: dynamically loads translations 2024-04-08 16:25:21 -04:00
277 changed files with 17171 additions and 27472 deletions

View File

@@ -221,8 +221,11 @@ jobs:
- email
- field-error-states
- fields-relationship
# - fields
- fields/lexical
- fields
- fields__collections__Blocks
- fields__collections__Array
- fields__collections__Relationship
- fields__collections__Lexical
- live-preview
- localization
- plugin-form-builder

View File

@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views/NotFound/index.js'
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: {
@@ -14,8 +14,8 @@ type Args = {
}
}
export const generateMetadata = ({ params }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params })
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
generatePageMetadata({ config, params, searchParams })
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })

View File

@@ -3,7 +3,7 @@ import type { Metadata } from 'next'
import config from '@payload-config'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { RootPage, generatePageMetadata } from '@payloadcms/next/views/Root/index.js'
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
type Args = {
params: {

View File

@@ -1,7 +1,7 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes/index.js'
import { REST_DELETE, REST_GET, REST_PATCH, REST_POST } from '@payloadcms/next/routes'
export const GET = REST_GET(config)
export const POST = REST_POST(config)

View File

@@ -1,6 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes/index.js'
import { GRAPHQL_PLAYGROUND_GET } from '@payloadcms/next/routes'
export const GET = GRAPHQL_PLAYGROUND_GET(config)

View File

@@ -1,6 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY it because it could be re-written at any time. */
import config from '@payload-config'
import { GRAPHQL_POST } from '@payloadcms/next/routes/index.js'
import { GRAPHQL_POST } from '@payloadcms/next/routes'
export const POST = GRAPHQL_POST(config)

View File

@@ -1,6 +1,6 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
import configPromise from '@payload-config'
import { RootLayout } from '@payloadcms/next/layouts/Root/index.js'
import { RootLayout } from '@payloadcms/next/layouts'
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import React from 'react'

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"private": true,
"type": "module",
"workspaces:": [

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"license": "MIT",
"type": "module",
"homepage": "https://payloadcms.com",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"description": "The officially supported MongoDB database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"description": "The officially supported Postgres database adapter for Payload",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"main": "./src/index.ts",
"types": "./src/index.d.ts",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"main": "./src/index.js",
"types": "./src/index.js",
"type": "module",

View File

@@ -1,13 +1,14 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { translations } from '@payloadcms/translations/client'
import { rtlLanguages } from '@payloadcms/translations'
import { initI18n } from '@payloadcms/translations'
import { RootProvider } from '@payloadcms/ui/providers/Root'
import '@payloadcms/ui/scss/app.scss'
import { buildComponentMap } from '@payloadcms/ui/utilities/buildComponentMap'
import { headers as getHeaders, cookies as nextCookies } from 'next/headers.js'
import { parseCookies } from 'payload/auth'
import { createClientConfig } from 'payload/config'
import { deepMerge } from 'payload/utilities'
import React from 'react'
import 'react-toastify/dist/ReactToastify.css'
@@ -20,8 +21,6 @@ export const metadata = {
title: 'Next.js',
}
const rtlLanguages = ['ar', 'fa', 'ha', 'ku', 'ur', 'ps', 'dv', 'ks', 'khw', 'he', 'yi']
export const RootLayout = async ({
children,
config: configPromise,
@@ -30,26 +29,36 @@ export const RootLayout = async ({
config: Promise<SanitizedConfig>
}) => {
const config = await configPromise
const clientConfig = await createClientConfig(config)
const headers = getHeaders()
const cookies = parseCookies(headers)
const lang =
getRequestLanguage({
config,
cookies,
headers,
}) ?? clientConfig.i18n.fallbackLanguage
const languageCode = getRequestLanguage({
config,
cookies,
headers,
})
const dir = rtlLanguages.includes(lang) ? 'RTL' : 'LTR'
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
const clientConfig = await createClientConfig({ config, t: i18n.t })
const mergedTranslations = deepMerge(translations, clientConfig.i18n.translations)
const dir = (rtlLanguages as unknown as AcceptedLanguages[]).includes(languageCode)
? 'RTL'
: 'LTR'
const languageOptions = Object.entries(translations || {}).map(([language, translations]) => ({
label: translations.general.thisLanguage,
value: language,
}))
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
async function switchLanguageServerAction(lang: string): Promise<void> {
@@ -66,20 +75,22 @@ export const RootLayout = async ({
DefaultListView,
children,
config,
i18n,
})
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={mergedTranslations[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'
@@ -22,12 +16,12 @@ if (!cached) {
cached = global._payload_fieldSchemaMap = null
}
export const getFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap => {
export const getFieldSchemaMap = (req: PayloadRequest): FieldSchemaMap => {
if (cached && process.env.NODE_ENV !== 'development') {
return cached
}
cached = buildFieldSchemaMap(config)
cached = buildFieldSchemaMap(req)
return cached
}
@@ -65,7 +59,7 @@ export const buildFormState = async ({ req }: { req: PayloadRequest }) => {
})
}
const fieldSchemaMap = getFieldSchemaMap(req.payload.config)
const fieldSchemaMap = getFieldSchemaMap(req)
const id = collectionSlug ? reqData.id : undefined
const schemaPathSegments = schemaPath.split('.')

View File

@@ -1,10 +1,13 @@
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 = (config: SanitizedConfig): FieldSchemaMap => {
export const buildFieldSchemaMap = ({
i18n,
payload: { config },
}: PayloadRequest): FieldSchemaMap => {
const result: FieldSchemaMap = new Map()
const validRelationships = config.collections.map((c) => c.slug) || []
@@ -13,6 +16,7 @@ export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap =>
traverseFields({
config,
fields: collection.fields,
i18n,
schemaMap: result,
schemaPath: collection.slug,
validRelationships,
@@ -23,6 +27,7 @@ export const buildFieldSchemaMap = (config: SanitizedConfig): FieldSchemaMap =>
traverseFields({
config,
fields: global.fields,
i18n,
schemaMap: result,
schemaPath: global.slug,
validRelationships,

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,6 +8,7 @@ import type { FieldSchemaMap } from './types.js'
type Args = {
config: SanitizedConfig
fields: Field[]
i18n: I18n
schemaMap: FieldSchemaMap
schemaPath: string
validRelationships: string[]
@@ -15,6 +17,7 @@ type Args = {
export const traverseFields = ({
config,
fields,
i18n,
schemaMap,
schemaPath,
validRelationships,
@@ -28,6 +31,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: field.fields,
i18n,
schemaMap,
schemaPath: `${schemaPath}.${field.name}`,
validRelationships,
@@ -39,6 +43,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: field.fields,
i18n,
schemaMap,
schemaPath,
validRelationships,
@@ -54,6 +59,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: block.fields,
i18n,
schemaMap,
schemaPath: blockSchemaPath,
validRelationships,
@@ -65,6 +71,7 @@ export const traverseFields = ({
if (typeof field.editor.generateSchemaMap === 'function') {
field.editor.generateSchemaMap({
config,
i18n,
schemaMap,
schemaPath: `${schemaPath}.${field.name}`,
})
@@ -83,6 +90,7 @@ export const traverseFields = ({
traverseFields({
config,
fields: tab.fields,
i18n,
schemaMap,
schemaPath: tabSchemaPath,
validRelationships,

View File

@@ -6,7 +6,6 @@ import type {
} from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
import { executeAuthStrategies } from 'payload/auth'
import { parseCookies } from 'payload/auth'
import { getDataLoader } from 'payload/utilities'
@@ -72,11 +71,10 @@ export const createPayloadRequest = async ({
headers: request.headers,
})
const i18n = initI18n({
const i18n = await initI18n({
config: config.i18n,
context: 'api',
language,
translations,
})
const customRequest: CustomPayloadRequest = {

View File

@@ -1,22 +1,20 @@
import type { I18n } from '@payloadcms/translations'
import type { AcceptedLanguages, I18n } from '@payloadcms/translations'
import type { SanitizedConfig } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/client'
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 =>
language?: AcceptedLanguages
}): Promise<I18n> =>
initI18n({
config: config.i18n,
context: 'client',
language: language || getRequestLanguage({ config, cookies: cookies(), headers: headers() }),
translations,
})

View File

@@ -1,3 +1,4 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies.js'
import type { SanitizedConfig } from 'payload/config'
@@ -6,7 +7,7 @@ import { matchLanguage } from '@payloadcms/translations'
type GetRequestLanguageArgs = {
config: SanitizedConfig
cookies: Map<string, string> | ReadonlyRequestCookies
defaultLanguage?: string
defaultLanguage?: AcceptedLanguages
headers: Request['headers']
}
@@ -15,14 +16,15 @@ export const getRequestLanguage = ({
cookies,
defaultLanguage = 'en',
headers,
}: GetRequestLanguageArgs): string => {
}: GetRequestLanguageArgs): AcceptedLanguages => {
const acceptLanguage = headers.get('Accept-Language')
const cookieLanguage = cookies.get(`${config.cookiePrefix || 'payload'}-lng`)
const reqLanguage =
(typeof cookieLanguage === 'string' ? cookieLanguage : cookieLanguage?.value) ||
acceptLanguage ||
config.i18n.fallbackLanguage ||
defaultLanguage
return matchLanguage(reqLanguage)
return matchLanguage(reqLanguage) || defaultLanguage
}

View File

@@ -8,7 +8,6 @@ import type {
} from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/client'
import { findLocaleFromCode } from '@payloadcms/ui/utilities/findLocaleFromCode'
import { headers as getHeaders } from 'next/headers.js'
import { notFound, redirect } from 'next/navigation.js'
@@ -45,14 +44,13 @@ 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,
translations,
})
const req = createLocalReq(
const req = await createLocalReq(
{
fallbackLocale: null,
locale: locale.code,

View File

@@ -16,6 +16,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
const {
req,
req: {
i18n,
payload: {
config,
config: {
@@ -51,6 +52,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
const createFirstUserFieldMap = mapFields({
config,
fieldSchema: fields,
i18n,
parentPath: userSlug,
})

View File

@@ -117,7 +117,10 @@ export const ListView: React.FC<AdminViewProps> = async ({ initPageResult, searc
<Fragment>
<HydrateClientUser permissions={permissions} user={user} />
<ListInfoProvider
collectionConfig={createClientCollectionConfig(collectionConfig)}
collectionConfig={createClientCollectionConfig({
collection: collectionConfig,
t: initPageResult.req.i18n.t,
})}
collectionSlug={collectionSlug}
hasCreatePermission={permissions?.collections?.[collectionSlug]?.create?.permission}
newDocumentURL={`${admin}/collections/${collectionSlug}/create`}

View File

@@ -2,12 +2,12 @@ import type { I18n } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type { AdminViewComponent, SanitizedConfig } from 'payload/types'
import { getNextI18n } from '@payloadcms/next/utilities'
import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
import { DefaultTemplate } from '@payloadcms/ui/templates/Default'
import React, { Fragment } from 'react'
import { initPage } from '../../utilities/initPage.js'
import { getNextI18n } from '.././../utilities/getNextI18n.js'
import { NotFoundClient } from './index.client.js'
export const generatePageMetadata = async ({
@@ -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

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT",
"main": "./src/index.ts",

View File

@@ -1,3 +1,4 @@
import type { I18n } from '@payloadcms/translations'
import type { JSONSchema4 } from 'json-schema'
import type { SanitizedConfig } from '../config/types.js'
@@ -28,10 +29,12 @@ type RichTextAdapterBase<
}) => Promise<void> | null
generateComponentMap: (args: {
config: SanitizedConfig
i18n: I18n
schemaPath: string
}) => Map<string, React.ReactNode>
generateSchemaMap?: (args: {
config: SanitizedConfig
i18n: I18n
schemaMap: Map<string, Field[]>
schemaPath: string
}) => Map<string, Field[]>

View File

@@ -1,6 +1,8 @@
import type React from 'react'
export type DescriptionFunction = () => string
import type { LabelFunction } from '../../config/types.js'
export type DescriptionFunction = LabelFunction
export type DescriptionComponent = React.ComponentType<FieldDescriptionProps>

View File

@@ -1,8 +1,10 @@
import type { LabelFunction } from '../../config/types.js'
export type LabelProps = {
CustomLabel?: React.ReactNode
as?: 'label' | 'span'
htmlFor?: string
label?: Record<string, string> | false | string
label?: LabelFunction | Record<string, string> | false | string
required?: boolean
unstyled?: boolean
}

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

@@ -2,15 +2,12 @@ import crypto from 'crypto'
import type { Field, FieldHook } from '../../fields/config/types.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
const labels = extractTranslations(['authentication:enableAPIKey', 'authentication:apiKey'])
const encryptKey: FieldHook = ({ req, value }) =>
value ? req.payload.encrypt(value as string) : null
const decryptKey: FieldHook = ({ req, value }) =>
value ? req.payload.decrypt(value as string) : undefined
// eslint-disable-next-line no-restricted-exports
export default [
{
name: 'enableAPIKey',
@@ -21,7 +18,7 @@ export default [
},
},
defaultValue: false,
label: labels['authentication:enableAPIKey'],
label: ({ t }) => t('authentication:enableAPIKey'),
},
{
name: 'apiKey',
@@ -35,7 +32,7 @@ export default [
afterRead: [decryptKey],
beforeChange: [encryptKey],
},
label: labels['authentication:apiKey'],
label: ({ t }) => t('authentication:apiKey'),
},
{
name: 'apiKeyIndex',

View File

@@ -1,9 +1,6 @@
import type { Field } from '../../fields/config/types.js'
import { email } from '../../fields/validations.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
const labels = extractTranslations(['general:email'])
const baseAuthFields: Field[] = [
{
@@ -14,7 +11,7 @@ const baseAuthFields: Field[] = [
Field: () => null,
},
},
label: labels['general:email'],
label: ({ t }) => t('general:email'),
required: true,
unique: true,
validate: email,

View File

@@ -1,9 +1,5 @@
import type { Field, FieldHook } from '../../fields/config/types.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
const labels = extractTranslations(['authentication:verified'])
const autoRemoveVerificationToken: FieldHook = ({ data, operation, originalDoc, value }) => {
// If a user manually sets `_verified` to true,
// and it was `false`, set _verificationToken to `null`.
@@ -33,7 +29,7 @@ export default [
Field: () => null,
},
},
label: labels['authentication:verified'],
label: ({ t }) => t('authentication:verified'),
},
{
name: '_verificationToken',

View File

@@ -1,9 +1,5 @@
import type { CollectionConfig } from '../collections/config/types.js'
import { extractTranslations } from '../translations/extractTranslations.js'
const labels = extractTranslations(['general:user', 'general:users'])
export const defaultUserCollection: CollectionConfig = {
slug: 'users',
admin: {
@@ -14,7 +10,7 @@ export const defaultUserCollection: CollectionConfig = {
},
fields: [],
labels: {
plural: labels['general:users'],
singular: labels['general:user'],
plural: ({ t }) => t('general:users'),
singular: ({ t }) => t('general:user'),
},
}

View File

@@ -10,6 +10,6 @@ export const auth = async (payload: Payload, options: AuthArgs): Promise<AuthRes
return await authOperation({
headers,
req: createLocalReq({ req: options.req as PayloadRequest }, payload),
req: await createLocalReq({ req: options.req as PayloadRequest }, payload),
})
}

View File

@@ -78,7 +78,6 @@ export const resolve: ResolveFn = async (specifier, context, nextResolve) => {
// and keep going
let nextResult: ResolveResult
// First, try to
if (!isTS) {
try {
nextResult = await nextResolve(specifier, context, nextResolve)

View File

@@ -23,14 +23,22 @@ export type ClientCollectionConfig = Omit<
fields: ClientFieldConfig[]
}
import type { TFunction } from '@payloadcms/translations'
import type { ClientFieldConfig } from '../../fields/config/client.js'
import type { SanitizedCollectionConfig } from './types.js'
import { createClientFieldConfigs } from '../../fields/config/client.js'
export const createClientCollectionConfig = (collection: SanitizedCollectionConfig) => {
export const createClientCollectionConfig = ({
collection,
t,
}: {
collection: SanitizedCollectionConfig
t: TFunction
}) => {
const sanitized = { ...collection }
sanitized.fields = createClientFieldConfigs(sanitized.fields)
sanitized.fields = createClientFieldConfigs({ fields: sanitized.fields, t })
const serverOnlyCollectionProperties: Partial<ServerOnlyCollectionProperties>[] = [
'hooks',
@@ -60,6 +68,14 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
delete sanitized.auth.verify
}
if (sanitized.labels) {
Object.entries(sanitized.labels).forEach(([labelType, collectionLabel]) => {
if (typeof collectionLabel === 'function') {
sanitized.labels[labelType] = collectionLabel({ t })
}
})
}
if ('admin' in sanitized) {
sanitized.admin = { ...sanitized.admin }
@@ -85,7 +101,11 @@ export const createClientCollectionConfig = (collection: SanitizedCollectionConf
return sanitized
}
export const createClientCollectionConfigs = (
collections: SanitizedCollectionConfig[],
): ClientCollectionConfig[] =>
collections.map((collection) => createClientCollectionConfig(collection))
export const createClientCollectionConfigs = ({
collections,
t,
}: {
collections: SanitizedCollectionConfig[]
t: TFunction
}): ClientCollectionConfig[] =>
collections.map((collection) => createClientCollectionConfig({ collection, t }))

View File

@@ -11,15 +11,12 @@ import TimestampsRequired from '../../errors/TimestampsRequired.js'
import { sanitizeFields } from '../../fields/config/sanitize.js'
import { fieldAffectsData } from '../../fields/config/types.js'
import mergeBaseFields from '../../fields/mergeBaseFields.js'
import { extractTranslations } from '../../translations/extractTranslations.js'
import { getBaseUploadFields } from '../../uploads/getBaseFields.js'
import { formatLabels } from '../../utilities/formatLabels.js'
import { isPlainObject } from '../../utilities/isPlainObject.js'
import baseVersionFields from '../../versions/baseFields.js'
import { authDefaults, defaults } from './defaults.js'
const translations = extractTranslations(['general:createdAt', 'general:updatedAt'])
const sanitizeCollection = (
config: Config,
collection: CollectionConfig,
@@ -51,7 +48,7 @@ const sanitizeCollection = (
disableBulkEdit: true,
hidden: true,
},
label: translations['general:updatedAt'],
label: ({ t }) => t('general:updatedAt'),
})
}
if (!hasCreatedAt) {
@@ -64,7 +61,7 @@ const sanitizeCollection = (
// The default sort for list view is createdAt. Thus, enabling indexing by default, is a major performance improvement, especially for large or a large amount of collections.
type: 'date',
index: true,
label: translations['general:createdAt'],
label: ({ t }) => t('general:createdAt'),
})
}
}

View File

@@ -140,10 +140,10 @@ const collectionSchema = joi.object().keys({
labels: joi.object({
plural: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
singular: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
}),
timestamps: joi.boolean(),
typescript: joi.object().keys({

View File

@@ -14,6 +14,7 @@ import type {
Endpoint,
EntityDescription,
GeneratePreviewURL,
LabelFunction,
LivePreviewConfig,
} from '../../config/types.js'
import type { Field } from '../../fields/config/types.js'
@@ -360,8 +361,8 @@ export type CollectionConfig = {
* Label configuration
*/
labels?: {
plural?: Record<string, string> | string
singular?: Record<string, string> | string
plural?: LabelFunction | Record<string, string> | string
singular?: LabelFunction | Record<string, string> | string
}
slug: string
/**

View File

@@ -34,6 +34,7 @@ export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
user?: Document
}
// eslint-disable-next-line no-restricted-exports
export default async function createLocal<TSlug extends keyof GeneratedTypes['collections']>(
payload: Payload,
options: Options<TSlug>,
@@ -58,7 +59,7 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
)
}
const req = createLocalReq(options, payload)
const req = await createLocalReq(options, payload)
req.file = file ?? (await getFileByPath(filePath))
return createOperation<TSlug>({

View File

@@ -50,7 +50,7 @@ export async function duplicate<TSlug extends keyof GeneratedTypes['collections'
)
}
const req = createLocalReq(options, payload)
const req = await createLocalReq(options, payload)
return duplicateOperation<TSlug>({
id,

View File

@@ -1,3 +1,5 @@
import type { TFunction } from '@payloadcms/translations'
import type { ClientCollectionConfig } from '../collections/config/client.js'
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
import type { ClientGlobalConfig } from '../globals/config/client.js'
@@ -41,10 +43,13 @@ export type ClientConfig = Omit<
globals: ClientGlobalConfig[]
}
export const createClientConfig = async (
configPromise: Promise<SanitizedConfig> | SanitizedConfig,
): Promise<ClientConfig> => {
const config = await configPromise
export const createClientConfig = async ({
config,
t,
}: {
config: SanitizedConfig
t: TFunction
}): Promise<ClientConfig> => {
const clientConfig: ClientConfig = { ...config }
const serverOnlyConfigProperties: Partial<ServerOnlyRootProperties>[] = [
@@ -95,11 +100,15 @@ export const createClientConfig = async (
}
}
clientConfig.collections = createClientCollectionConfigs(
clientConfig.collections as SanitizedCollectionConfig[],
)
clientConfig.collections = createClientCollectionConfigs({
collections: clientConfig.collections as SanitizedCollectionConfig[],
t,
})
clientConfig.globals = createClientGlobalConfigs(clientConfig.globals as SanitizedGlobalConfig[])
clientConfig.globals = createClientGlobalConfigs({
globals: clientConfig.globals as SanitizedGlobalConfig[],
t,
})
return clientConfig
}

View File

@@ -1,4 +1,4 @@
import { translations } from '@payloadcms/translations/api'
import { en } from '@payloadcms/translations/languages/en'
import merge from 'deepmerge'
import type {
@@ -87,8 +87,10 @@ export const sanitizeConfig = (incomingConfig: Config): SanitizedConfig => {
config.i18n = {
fallbackLanguage: 'en',
supportedLanguages: Object.keys(translations),
translations,
supportedLanguages: {
en,
},
translations: {},
...(incomingConfig?.i18n ?? {}),
}

View File

@@ -1,4 +1,4 @@
import type { I18nOptions } from '@payloadcms/translations'
import type { I18nOptions, TFunction } from '@payloadcms/translations'
import type { Options as ExpressFileUploadOptions } from 'express-fileupload'
import type GraphQL from 'graphql'
import type { Transporter } from 'nodemailer'
@@ -363,6 +363,8 @@ export type LocalizationConfig = Prettify<
LocalizationConfigWithLabels | LocalizationConfigWithNoLabels
>
export type LabelFunction = ({ t }: { t: TFunction }) => string
export type SharpDependency = (
input?:
| ArrayBuffer
@@ -671,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 { translations } from '@payloadcms/translations/api'
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') : translations.en.error.emailOrPasswordIncorrect,
t ? t('error:emailOrPasswordIncorrect') : en.translations.error.emailOrPasswordIncorrect,
httpStatus.UNAUTHORIZED,
)
}

View File

@@ -1,6 +1,6 @@
import type { TFunction } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
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 ErrorDeletingFile extends APIError {
constructor(t?: TFunction) {
super(
t ? t('error:deletingFile') : translations.en.error.deletingFile,
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 { translations } from '@payloadcms/translations/api'
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') : translations.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 { translations } from '@payloadcms/translations/api'
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') : translations.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 { translations } from '@payloadcms/translations/api'
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') : translations.en.error.userLocked, httpStatus.UNAUTHORIZED)
super(t ? t('error:userLocked') : en.translations.error.userLocked, httpStatus.UNAUTHORIZED)
}
}

View File

@@ -1,6 +1,6 @@
import type { TFunction } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
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 MissingFile extends APIError {
constructor(t?: TFunction) {
super(
t ? t('error:noFilesUploaded') : translations.en.error.noFilesUploaded,
t ? t('error:noFilesUploaded') : en.translations.error.noFilesUploaded,
httpStatus.BAD_REQUEST,
)
}

View File

@@ -1,13 +1,13 @@
import type { TFunction } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
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') : translations.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 { translations } from '@payloadcms/translations/api'
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') : translations.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 { translations } from '@payloadcms/translations/api'
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
? translations.en.error.followingFieldsInvalid_one
: translations.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

@@ -1 +1,2 @@
export { generateTypes } from '../bin/generateTypes.js'
export { importConfig, importWithoutClientFiles } from '../utilities/importWithoutClientFiles.js'

View File

@@ -2,7 +2,6 @@ export { getDataLoader } from '../collections/dataloader.js'
export { default as getDefaultValue } from '../fields/getDefaultValue.js'
export { promise as afterReadPromise } from '../fields/hooks/afterRead/promise.js'
export { traverseFields as afterReadTraverseFields } from '../fields/hooks/afterRead/traverseFields.js'
export { extractTranslations } from '../translations/extractTranslations.js'
export { formatFilesize } from '../uploads/formatFilesize.js'

View File

@@ -1,3 +1,5 @@
import type { TFunction } from '@payloadcms/translations'
import type { Field, FieldBase } from '../../fields/config/types.js'
export type ClientFieldConfig = Omit<Field, 'access' | 'defaultValue' | 'hooks' | 'validate'>
@@ -13,8 +15,14 @@ export type ServerOnlyFieldAdminProperties = keyof Pick<
'components' | 'condition' | 'description'
>
export const createClientFieldConfig = (f: Field) => {
const field = { ...f }
export const createClientFieldConfig = ({
field: incomingField,
t,
}: {
field: Field
t: TFunction
}) => {
const field = { ...incomingField }
const serverOnlyFieldProperties: Partial<ServerOnlyFieldProperties>[] = [
'hooks',
@@ -37,21 +45,34 @@ export const createClientFieldConfig = (f: Field) => {
}
})
if ('options' in field && Array.isArray(field.options)) {
field.options = field.options.map((option) => {
if (typeof option === 'object' && typeof option.label === 'function') {
return {
label: option.label({ t }),
value: option.value,
}
}
return option
})
}
if ('fields' in field) {
field.fields = createClientFieldConfigs(field.fields)
field.fields = createClientFieldConfigs({ fields: field.fields, t })
}
if ('blocks' in field) {
field.blocks = field.blocks.map((block) => {
const sanitized = { ...block }
sanitized.fields = createClientFieldConfigs(sanitized.fields)
sanitized.fields = createClientFieldConfigs({ fields: sanitized.fields, t })
return sanitized
})
}
if ('tabs' in field) {
// @ts-expect-error
field.tabs = field.tabs.map((tab) => createClientFieldConfig(tab))
field.tabs = field.tabs.map((tab) => createClientFieldConfig({ field: tab, t }))
}
if ('admin' in field) {
@@ -73,5 +94,10 @@ export const createClientFieldConfig = (f: Field) => {
return field
}
export const createClientFieldConfigs = (fields: Field[]): Field[] =>
fields.map(createClientFieldConfig)
export const createClientFieldConfigs = ({
fields,
t,
}: {
fields: Field[]
t: TFunction
}): Field[] => fields.map((field) => createClientFieldConfig({ field, t }))

View File

@@ -52,7 +52,12 @@ export const baseField = joi
index: joi.boolean().default(false),
label: joi
.alternatives()
.try(joi.object().pattern(joi.string(), [joi.string()]), joi.string(), joi.valid(false)),
.try(
joi.func(),
joi.object().pattern(joi.string(), [joi.string()]),
joi.string(),
joi.valid(false),
),
localized: joi.boolean().default(false),
required: joi.boolean().default(false),
saveToJWT: joi.alternatives().try(joi.boolean(), joi.string()).default(false),
@@ -216,7 +221,7 @@ export const select = baseField.keys({
joi.object({
label: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
value: joi.string().required().allow(''),
}),
),
@@ -245,7 +250,7 @@ export const radio = baseField.keys({
joi.object({
label: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]))
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()]))
.required(),
value: joi.string().required().allow(''),
}),

View File

@@ -17,6 +17,7 @@ import type {
} from '../../admin/types.js'
import type { User } from '../../auth/index.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js'
import type { LabelFunction } from '../../config/types.js'
import type { DBIdentifierName } from '../../database/types.js'
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
@@ -134,8 +135,8 @@ type Admin = {
}
export type Labels = {
plural: Record<string, string> | string
singular: Record<string, string> | string
plural: LabelFunction | Record<string, string> | string
singular: LabelFunction | Record<string, string> | string
}
export type BaseValidateOptions<TData, TSiblingData> = {
@@ -165,7 +166,7 @@ export type Validate<
export type ClientValidate = Omit<Validate, 'req'>
export type OptionObject = {
label: Record<string, string> | string
label: LabelFunction | Record<string, string> | string
value: string
}
@@ -193,7 +194,7 @@ export interface FieldBase {
beforeValidate?: FieldHook[]
}
index?: boolean
label?: Record<string, string> | false | string
label?: LabelFunction | Record<string, string> | false | string
localized?: boolean
name: string
required?: boolean
@@ -394,6 +395,7 @@ export type UnnamedTab = Omit<TabBase, 'name'> & {
| {
[selectedLanguage: string]: string
}
| LabelFunction
| string
localized?: never
}

View File

@@ -1,16 +1,12 @@
// default beforeDuplicate hook for required and unique fields
import type { FieldAffectingData, FieldHook } from './config/types.js'
import { extractTranslations } from '../translations/extractTranslations.js'
const copyTranslations = extractTranslations(['general:copy'])
const unique: FieldHook = ({ value }) => (typeof value === 'string' ? `${value} - Copy` : undefined)
const localizedUnique: FieldHook = ({ req, value }) =>
value ? `${value} - ${copyTranslations?.[req.locale]?.['general:copy'] ?? 'Copy'}` : undefined
value ? `${value} - ${req?.t('general:copy') ?? 'Copy'}` : undefined
const uniqueRequired: FieldHook = ({ value }) => `${value} - Copy`
const localizedUniqueRequired: FieldHook = ({ req, value }) =>
`${value} - ${copyTranslations?.[req.locale]?.['general:copy'] ?? 'Copy'}`
`${value} - ${req?.t('general:copy') ?? 'Copy'}`
export const setDefaultBeforeDuplicate = (field: FieldAffectingData) => {
if (

View File

@@ -1,3 +1,5 @@
import type { TFunction } from '@payloadcms/translations'
import type {
LivePreviewConfig,
SanitizedConfig,
@@ -30,11 +32,15 @@ export type ClientGlobalConfig = Omit<
fields: ClientFieldConfig[]
}
export const createClientGlobalConfig = (
global: SanitizedConfig['globals'][0],
): ClientGlobalConfig => {
export const createClientGlobalConfig = ({
global,
t,
}: {
global: SanitizedConfig['globals'][0]
t: TFunction
}): ClientGlobalConfig => {
const sanitized = { ...global }
sanitized.fields = createClientFieldConfigs(sanitized.fields)
sanitized.fields = createClientFieldConfigs({ fields: sanitized.fields, t })
const serverOnlyProperties: Partial<ServerOnlyGlobalProperties>[] = [
'hooks',
@@ -73,6 +79,10 @@ export const createClientGlobalConfig = (
return sanitized
}
export const createClientGlobalConfigs = (
globals: SanitizedConfig['globals'],
): ClientGlobalConfig[] => globals.map((global) => createClientGlobalConfig(global))
export const createClientGlobalConfigs = ({
globals,
t,
}: {
globals: SanitizedConfig['globals']
t: TFunction
}): ClientGlobalConfig[] => globals.map((global) => createClientGlobalConfig({ global, t }))

View File

@@ -1,5 +1,3 @@
import { extractTranslations } from 'payload/utilities'
import type { Config } from '../../config/types.js'
import type { SanitizedGlobalConfig } from './types.js'
@@ -10,8 +8,6 @@ import mergeBaseFields from '../../fields/mergeBaseFields.js'
import { toWords } from '../../utilities/formatLabels.js'
import baseVersionFields from '../../versions/baseFields.js'
const translations = extractTranslations(['general:createdAt', 'general:updatedAt'])
const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
const { collections, globals } = config
@@ -80,7 +76,7 @@ const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
disableBulkEdit: true,
hidden: true,
},
label: translations['general:updatedAt'],
label: ({ t }) => t('general:updatedAt'),
})
}
if (!hasCreatedAt) {
@@ -91,7 +87,7 @@ const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
disableBulkEdit: true,
hidden: true,
},
label: translations['general:createdAt'],
label: ({ t }) => t('general:createdAt'),
})
}

View File

@@ -65,7 +65,9 @@ const globalSchema = joi
beforeRead: joi.array().items(joi.func()),
beforeValidate: joi.array().items(joi.func()),
}),
label: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
label: joi
.alternatives()
.try(joi.func(), joi.string(), joi.object().pattern(joi.string(), [joi.string()])),
typescript: joi.object().keys({
interface: joi.string(),
}),

View File

@@ -20,6 +20,7 @@ export type Options<T extends keyof GeneratedTypes['globals']> = {
user?: Document
}
// eslint-disable-next-line no-restricted-exports
export default async function findVersionByIDLocal<T extends keyof GeneratedTypes['globals']>(
payload: Payload,
options: Options<T>,

View File

@@ -1,19 +0,0 @@
import { translations } from '@payloadcms/translations/api'
export const extractTranslations = (keys: string[]): Record<string, Record<string, string>> => {
const result = {}
keys.forEach((key) => {
result[key] = {}
})
Object.entries(translations).forEach(([language, resource]) => {
keys.forEach((key) => {
const [section, target] = key.split(':')
if (resource?.[section]?.[target]) {
result[key][language] = resource[section][target]
} else {
// console.error(`Missing translation for ${key} in ${language}`)
}
})
})
return result
}

View File

@@ -1,18 +1,18 @@
import type { AcceptedLanguages } from '@payloadcms/translations'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/api'
import type { SanitizedConfig } from '../config/types.js'
export const getLocalI18n = ({
export const getLocalI18n = async ({
config,
language = 'en',
}: {
config: SanitizedConfig
language?: string
language?: AcceptedLanguages
}) =>
initI18n({
config: config.i18n,
context: 'api',
language,
translations,
})

View File

@@ -3,17 +3,8 @@ import type { Config } from '../config/types.js'
import type { Field } from '../fields/config/types.js'
import type { UploadConfig } from './types.js'
import { extractTranslations } from '../translations/extractTranslations.js'
import { mimeTypeValidator } from './mimeTypeValidator.js'
const labels = extractTranslations([
'upload:width',
'upload:height',
'upload:fileSize',
'upload:fileName',
'upload:sizes',
])
type GenerateURLArgs = {
collectionSlug: string
config: Config
@@ -87,7 +78,7 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
hidden: true,
readOnly: true,
},
label: labels['upload:width'],
label: ({ t }) => t('upload:width'),
}
const height: Field = {
@@ -97,7 +88,7 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
hidden: true,
readOnly: true,
},
label: labels['upload:height'],
label: ({ t }) => t('upload:height'),
}
const filesize: Field = {
@@ -107,7 +98,7 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
hidden: true,
readOnly: true,
},
label: labels['upload:fileSize'],
label: ({ t }) => t('upload:fileSize'),
}
const filename: Field = {
@@ -119,7 +110,7 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
readOnly: true,
},
index: true,
label: labels['upload:fileName'],
label: ({ t }) => t('upload:fileName'),
unique: true,
}
@@ -201,7 +192,7 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
],
label: size.name,
})),
label: labels['upload:Sizes'],
label: ({ t }) => t('upload:Sizes'),
},
])
}

View File

@@ -63,12 +63,12 @@ type CreateLocalReq = (
user?: User
},
payload: Payload,
) => PayloadRequest
export const createLocalReq: CreateLocalReq = (
) => Promise<PayloadRequest>
export const createLocalReq: CreateLocalReq = async (
{ context, fallbackLocale, locale: localeArg, req = {} as PayloadRequest, user },
payload,
) => {
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
const i18n = req?.i18n || (await getLocalI18n({ config: payload.config }))
if (payload.config?.localization) {
const locale = localeArg === '*' ? 'all' : localeArg

View File

@@ -1,16 +1,12 @@
import type { Field } from '../fields/config/types.js'
import { extractTranslations } from '../translations/extractTranslations.js'
const labels = extractTranslations(['version:draft', 'version:published', 'version:status'])
export const statuses = [
{
label: labels['version:draft'],
label: ({ t }) => t('version:draft'),
value: 'draft',
},
{
label: labels['version:published'],
label: ({ t }) => t('version:published'),
value: 'published',
},
]
@@ -26,7 +22,7 @@ const baseVersionFields: Field[] = [
disableBulkEdit: true,
},
defaultValue: 'draft',
label: labels['version:status'],
label: ({ t }) => t('version:status'),
options: statuses,
},
]

2
packages/payload/uploads.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
export { getFileByPath } from './dist/uploads/getFileByPath.js';
//# sourceMappingURL=uploads.d.ts.map

View File

@@ -0,0 +1,3 @@
export { getFileByPath } from './dist/uploads/getFileByPath.js';
//# sourceMappingURL=uploads.js.map

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"description": "The official cloud storage plugin for Payload CMS",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"main": "./src/index.ts",
"types": "./src/index.ts",
"type": "module",

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-cloud",
"description": "The official Payload Cloud plugin",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"main": "./src/index.ts",
"types": "./src/index.ts",
"license": "MIT",

View File

@@ -1,7 +1,7 @@
{
"name": "@payloadcms/plugin-form-builder",
"description": "Form builder plugin for Payload CMS",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"description": "The official Nested Docs plugin for Payload",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"homepage:": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"description": "The officially supported Lexical richtext adapter for Payload",
"repository": {
"type": "git",

View File

@@ -2,21 +2,8 @@ import type { User } from 'payload/auth'
import type { Config } from 'payload/config'
import type { Field, RadioField, TextField } from 'payload/types'
import { extractTranslations } from 'payload/utilities'
import { validateUrl } from '../../../lexical/utils/url.js'
const translations = extractTranslations([
'fields:textToDisplay',
'fields:linkType',
'fields:chooseBetweenCustomTextOrDocument',
'fields:customURL',
'fields:internalLink',
'fields:enterURL',
'fields:chooseDocumentToLink',
'fields:openInNewTab',
])
export const getBaseFields = (
config: Config,
enabledCollections: false | string[],
@@ -49,7 +36,7 @@ export const getBaseFields = (
{
name: 'text',
type: 'text',
label: translations['fields:textToDisplay'],
label: ({ t }) => t('fields:textToDisplay'),
required: true,
},
{
@@ -68,13 +55,13 @@ export const getBaseFields = (
name: 'linkType',
type: 'radio',
admin: {
description: translations['fields:chooseBetweenCustomTextOrDocument'],
description: ({ t }) => t('fields:chooseBetweenCustomTextOrDocument'),
},
defaultValue: 'custom',
label: translations['fields:linkType'],
label: ({ t }) => t('fields:linkType'),
options: [
{
label: translations['fields:customURL'],
label: ({ t }) => t('fields:customURL'),
value: 'custom',
},
],
@@ -83,7 +70,7 @@ export const getBaseFields = (
{
name: 'url',
type: 'text',
label: translations['fields:enterURL'],
label: ({ t }) => t('fields:enterURL'),
required: true,
validate: (value: string) => {
if (value && !validateUrl(value)) {
@@ -98,7 +85,7 @@ export const getBaseFields = (
// Only display internal link-specific fields / options / conditions if there are enabled relations
if (enabledRelations?.length) {
;(baseFields[1].fields[0] as RadioField).options.push({
label: translations['fields:internalLink'],
label: ({ t }) => t('fields:internalLink'),
value: 'internal',
})
;(baseFields[1].fields[1] as TextField).admin = {
@@ -123,7 +110,7 @@ export const getBaseFields = (
}
}
: null,
label: translations['fields:chooseDocumentToLink'],
label: ({ t }) => t('fields:chooseDocumentToLink'),
relationTo: enabledRelations,
required: true,
})
@@ -132,7 +119,7 @@ export const getBaseFields = (
baseFields[1].fields.push({
name: 'newTab',
type: 'checkbox',
label: translations['fields:openInNewTab'],
label: ({ t }) => t('fields:openInNewTab'),
})
return baseFields as Field[]

View File

@@ -2,9 +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 { translations } from '@payloadcms/translations/client'
import type { HTMLConverter } from '../converters/html/converter/types.js'
import type { FeatureProviderProviderServer } from '../types.js'
import type { ClientProps } from './feature.client.js'
@@ -67,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', translations })
generateSchemaMap: ({ config, i18n, props }) => {
return {
fields: transformExtraFields(
props.fields,

View File

@@ -246,10 +246,10 @@ export const TOGGLE_LINK_COMMAND: LexicalCommand<LinkPayload | null> =
export function toggleLink(payload: LinkPayload): void {
const selection = $getSelection()
if (!$isRangeSelection(selection)) {
if (!$isRangeSelection(selection) && !payload.selectedNodes.length) {
return
}
const nodes = selection.extract()
const nodes = $isRangeSelection(selection) ? selection.extract() : payload.selectedNodes
if (payload === null) {
// Remove LinkNodes

View File

@@ -8,6 +8,8 @@ const { useLexicalComposerContext } = lexicalComposerContextImport
import lexicalUtilsImport from '@lexical/utils'
const { $findMatchingParent, mergeRegister } = lexicalUtilsImport
import type { LexicalNode } from 'lexical'
import { getTranslation } from '@payloadcms/translations'
import lexicalImport from 'lexical'
const {
@@ -57,6 +59,8 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
const { closeModal, toggleModal } = useModal()
const editDepth = useEditDepth()
const [isLink, setIsLink] = useState(false)
const [selectedNodes, setSelectedNodes] = useState<LexicalNode[]>([])
const [isAutoLink, setIsAutoLink] = useState(false)
const drawerSlug = formatDrawerSlug({
@@ -78,6 +82,7 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
setIsAutoLink(false)
setLinkUrl('')
setLinkLabel('')
setSelectedNodes([])
return
}
@@ -115,6 +120,8 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
setStateData(data)
setIsLink(true)
setSelectedNodes(selection ? selection?.getNodes() : [])
if ($isAutoLinkNode(linkParent)) {
setIsAutoLink(true)
} else {
@@ -291,18 +298,26 @@ export function LinkEditor({ anchorElem }: { anchorElem: HTMLElement }): React.R
const newLinkPayload: LinkPayload = data as LinkPayload
newLinkPayload.selectedNodes = selectedNodes
// See: https://github.com/facebook/lexical/pull/5536. This updates autolink nodes to link nodes whenever a change was made (which is good!).
editor.update(() => {
const selection = $getSelection()
let linkParent = null
if ($isRangeSelection(selection)) {
const parent = getSelectedNode(selection).getParent()
if ($isAutoLinkNode(parent)) {
const linkNode = $createLinkNode({
fields: newLinkPayload.fields,
})
parent.replace(linkNode, true)
linkParent = getSelectedNode(selection).getParent()
} else {
if (selectedNodes.length) {
linkParent = selectedNodes[0].getParent()
}
}
if (linkParent && $isAutoLinkNode(linkParent)) {
const linkNode = $createLinkNode({
fields: newLinkPayload.fields,
})
linkParent.replace(linkNode, true)
}
})
// Needs to happen AFTER a potential auto link => link node conversion, as otherwise, the updated text to display may be lost due to

View File

@@ -1,3 +1,5 @@
import type { LexicalNode } from 'lexical'
import type { LinkFields } from '../../nodes/types.js'
/**
@@ -6,6 +8,7 @@ import type { LinkFields } from '../../nodes/types.js'
*/
export type LinkPayload = {
fields: LinkFields
selectedNodes?: LexicalNode[]
/**
* The text content of the link node - will be displayed in the drawer
*/

View File

@@ -7,6 +7,7 @@ import { getBaseFields } from '../../drawer/baseFields.js'
/**
* This function is run to enrich the basefields which every link has with potential, custom user-added fields.
*/
// eslint-disable-next-line @typescript-eslint/require-await
export function transformExtraFields(
customFieldSchema:
| ((args: { config: SanitizedConfig; defaultFields: Field[]; i18n: I18n }) => Field[])

View File

@@ -19,7 +19,7 @@ type FilteredCollectionsT = (
const filterRichTextCollections: FilteredCollectionsT = (collections, options) => {
return collections.filter(({ slug, admin: { enableRichTextRelationship }, upload }) => {
if (options.visibleEntities.collections.includes(slug)) {
if (!options.visibleEntities.collections.includes(slug)) {
return false
}

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[]
}
}) =>
| {
[key: string]: Field[]
}
| Promise<{
[key: string]: Field[]
}>
generatedTypes?: {
modifyOutputSchema: ({
collectionIDFieldTypes,

View File

@@ -13,7 +13,7 @@ export const getGenerateComponentMap =
(args: {
resolvedFeatureMap: ResolvedServerFeatureMap
}): RichTextAdapter['generateComponentMap'] =>
({ config, schemaPath }) => {
({ config, i18n, schemaPath }) => {
const validRelationships = config.collections.map((c) => c.slug) || []
const componentMap = new Map()
@@ -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,
@@ -87,6 +89,7 @@ export const getGenerateComponentMap =
config,
disableAddingID: true,
fieldSchema: sanitizedFields,
i18n,
parentPath: `${schemaPath}.feature.${featureKey}.fields.${schemaKey}`,
readOnly: false,
})

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,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"description": "The officially supported Slate richtext adapter for Payload",
"repository": {
"type": "git",

View File

@@ -2,41 +2,28 @@ import type { User } from 'payload/auth'
import type { Config } from 'payload/config'
import type { Field } from 'payload/types'
import { extractTranslations } from 'payload/utilities'
const translations = extractTranslations([
'fields:textToDisplay',
'fields:linkType',
'fields:chooseBetweenCustomTextOrDocument',
'fields:customURL',
'fields:internalLink',
'fields:enterURL',
'fields:chooseDocumentToLink',
'fields:openInNewTab',
])
export const getBaseFields = (config: Config): Field[] => [
{
name: 'text',
type: 'text',
label: translations['fields:textToDisplay'],
label: ({ t }) => t('fields:textToDisplay'),
required: true,
},
{
name: 'linkType',
type: 'radio',
admin: {
description: translations['fields:chooseBetweenCustomTextOrDocument'],
description: ({ t }) => t('fields:chooseBetweenCustomTextOrDocument'),
},
defaultValue: 'custom',
label: translations['fields:linkType'],
label: ({ t }) => t('fields:linkType'),
options: [
{
label: translations['fields:customURL'],
label: ({ t }) => t('fields:customURL'),
value: 'custom',
},
{
label: translations['fields:internalLink'],
label: ({ t }) => t('fields:internalLink'),
value: 'internal',
},
],
@@ -48,7 +35,7 @@ export const getBaseFields = (config: Config): Field[] => [
admin: {
condition: ({ linkType }) => linkType !== 'internal',
},
label: translations['fields:enterURL'],
label: ({ t }) => t('fields:enterURL'),
required: true,
},
{
@@ -66,7 +53,7 @@ export const getBaseFields = (config: Config): Field[] => [
return false
}
},
label: translations['fields:chooseDocumentToLink'],
label: ({ t }) => t('fields:chooseDocumentToLink'),
relationTo: config.collections
.filter(({ admin: { enableRichTextLink, hidden } }) => {
if (typeof hidden !== 'function' && hidden) {
@@ -80,6 +67,6 @@ export const getBaseFields = (config: Config): Field[] => [
{
name: 'newTab',
type: 'checkbox',
label: translations['fields:openInNewTab'],
label: ({ t }) => t('fields:openInNewTab'),
},
]

View File

@@ -1,7 +1,5 @@
import type { RichTextAdapter } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/client'
import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap'
import { sanitizeFields } from 'payload/config'
import React from 'react'
@@ -16,10 +14,9 @@ import { defaultLeaves as leafTypes } from './field/leaves/index.js'
export const getGenerateComponentMap =
(args: AdapterArguments): RichTextAdapter['generateComponentMap'] =>
({ config }) => {
({ config, i18n }) => {
const componentMap = new Map()
const i18n = initI18n({ config: config.i18n, context: 'client', translations })
const validRelationships = config.collections.map((c) => c.slug) || []
;(args?.admin?.leaves || Object.values(leafTypes)).forEach((leaf) => {
@@ -78,6 +75,7 @@ export const getGenerateComponentMap =
const mappedFields = mapFields({
config,
fieldSchema: linkFields,
i18n,
readOnly: false,
})
@@ -108,6 +106,7 @@ export const getGenerateComponentMap =
const mappedFields = mapFields({
config,
fieldSchema: uploadFields,
i18n,
readOnly: false,
})

View File

@@ -1,7 +1,5 @@
import type { RichTextAdapter } from 'payload/types'
import { initI18n } from '@payloadcms/translations'
import { translations } from '@payloadcms/translations/client'
import { sanitizeFields } from 'payload/config'
import type { AdapterArguments, RichTextCustomElement } from './types.js'
@@ -13,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', translations })
({ config, i18n, schemaMap, schemaPath }) => {
const validRelationships = config.collections.map((c) => c.slug) || []
;(args?.admin?.elements || Object.values(elementTypes)).forEach((el) => {

View File

@@ -43,3 +43,121 @@ These are the translations for Payload. Translations are used on both the server
// or
pnpm build
```
Here is a full list of language keys. Note that these are not all implemented, but if you would like to contribute and add a new language, you can use this list as a reference:
| Language Code | Language Name |
| -------------- | ------------------------------------------ |
| af | Afrikaans |
| am | Amharic |
| ar-sa | Arabic (Saudi Arabia) |
| as | Assamese |
| az-Latn | Azerbaijani (Latin) |
| be | Belarusian |
| bg | Bulgarian |
| bn-BD | Bangla (Bangladesh) |
| bn-IN | Bangla (India) |
| bs | Bosnian (Latin) |
| ca | Catalan Spanish |
| ca-ES-valencia | Valencian |
| cs | Czech |
| cy | Welsh |
| da | Danish |
| de | German (Germany) |
| el | Greek |
| en-GB | English (United Kingdom) |
| en-US | English (United States) |
| es | Spanish (Spain) |
| es-ES | Spanish (Spain) |
| es-US | Spanish (United States) |
| es-MX | Spanish (Mexico) |
| et | Estonian |
| eu | Basque |
| fa | Persian |
| fi | Finnish |
| fil-Latn | Filipino |
| fr | French (France) |
| fr-FR | French (France) |
| fr-CA | French (Canada) |
| ga | Irish |
| gd-Latn | Scottish Gaelic |
| gl | Galician |
| gu | Gujarati |
| ha-Latn | Hausa (Latin) |
| he | Hebrew |
| hi | Hindi |
| hr | Croatian |
| hu | Hungarian |
| hy | Armenian |
| id | Indonesian |
| ig-Latn | Igbo |
| is | Icelandic |
| it | Italian (Italy) |
| it-it | Italian (Italy) |
| ja | Japanese |
| ka | Georgian |
| kk | Kazakh |
| km | Khmer |
| kn | Kannada |
| ko | Korean |
| kok | Konkani |
| ku-Arab | Central Kurdish |
| ky-Cyrl | Kyrgyz |
| lb | Luxembourgish |
| lt | Lithuanian |
| lv | Latvian |
| mi-Latn | Maori |
| mk | Macedonian |
| ml | Malayalam |
| mn-Cyrl | Mongolian (Cyrillic) |
| mr | Marathi |
| ms | Malay (Malaysia) |
| mt | Maltese |
| nb | Norwegian (Bokmål) |
| ne | Nepali (Nepal) |
| nl | Dutch (Netherlands) |
| nl-BE | Dutch (Netherlands) |
| nn | Norwegian (Nynorsk) |
| nso | Sesotho sa Leboa |
| or | Odia |
| pa | Punjabi (Gurmukhi) |
| pa-Arab | Punjabi (Arabic) |
| pl | Polish |
| prs-Arab | Dari |
| pt-BR | Portuguese (Brazil) |
| pt-PT | Portuguese (Portugal) |
| qut-Latn | Kiche |
| quz | Quechua (Peru) |
| ro | Romanian (Romania) |
| ru | Russian |
| rw | Kinyarwanda |
| sd-Arab | Sindhi (Arabic) |
| si | Sinhala |
| sk | Slovak |
| sl | Slovenian |
| sq | Albanian |
| sr-Cyrl-BA | Serbian (Cyrillic, Bosnia and Herzegovina) |
| sr-Cyrl-RS | Serbian (Cyrillic, Serbia) |
| sr-Latn-RS | Serbian (Latin, Serbia) |
| sv | Swedish (Sweden) |
| sw | Kiswahili |
| ta | Tamil |
| te | Telugu |
| tg-Cyrl | Tajik (Cyrillic) |
| th | Thai |
| ti | Tigrinya |
| tk-Latn | Turkmen (Latin) |
| tn | Setswana |
| tr | Turkish |
| tt-Cyrl | Tatar (Cyrillic) |
| ug-Arab | Uyghur |
| uk | Ukrainian |
| ur | Urdu |
| uz-Latn | Uzbek (Latin) |
| vi | Vietnamese |
| wo | Wolof |
| xh | isiXhosa |
| yo-Latn | Yoruba |
| zh-Hans | Chinese (Simplified) |
| zh-Hant | Chinese (Traditional) |
| zu | isiZulu |

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-alpha.60",
"version": "3.0.0-alpha.61",
"main": "./src/exports/index.ts",
"types": "./src/types.ts",
"type": "module",
@@ -12,9 +12,8 @@
},
"scripts": {
"build:types": "tsc --outDir dist",
"build": "pnpm writeFiles && pnpm build:types",
"build": "pnpm build:types",
"clean": "rimraf {dist,*.tsbuildinfo}",
"writeFiles": "npx tsx ./writeTranslationFiles.ts",
"prepublishOnly": "pnpm clean && pnpm turbo build"
},
"exports": {
@@ -22,13 +21,13 @@
"import": "./src/exports/index.ts",
"require": "./src/exports/index.ts"
},
"./api": {
"import": "./src/_generatedFiles_/api/index.ts",
"require": "./src/_generatedFiles_/api/index.ts"
"./*": {
"import": "./src/exports/*.ts",
"require": "./src/exports/*.ts"
},
"./client": {
"import": "./src/_generatedFiles_/client/index.ts",
"require": "./src/_generatedFiles_/client/index.ts"
"./languages/*": {
"import": "./src/languages/*.ts",
"require": "./src/languages/*.ts"
}
},
"publishConfig": {
@@ -41,15 +40,15 @@
"require": "./dist/exports/index.js",
"types": "./dist/exports/*.d.ts"
},
"./api": {
"import": "./dist/_generatedFiles_/api/index.js",
"require": "./dist/_generatedFiles_/api/index.js",
"types": "./dist/_generatedFiles_/exports/*.d.ts"
"./*": {
"import": "./dist/exports/*.js",
"require": "./dist/exports/*.js",
"types": "./dist/exports/*.d.ts"
},
"./client": {
"import": "./dist/_generatedFiles_/client/index.js",
"require": "./dist/_generatedFiles_/client/index.js",
"types": "./dist/_generatedFiles_/exports/*.d.ts"
"./languages/*": {
"import": "./dist/languages/*.js",
"require": "./dist/languages/*.js",
"types": "./dist/languages/*.d.ts"
}
}
},
@@ -57,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": [

Some files were not shown because too many files have changed in this diff Show More