adds Translation component and removes more react-i18next

This commit is contained in:
Jarrod Flesch
2024-01-16 09:36:35 -05:00
parent 0bc7c452c3
commit 13313028b5
43 changed files with 292 additions and 211 deletions

View File

@@ -18,6 +18,7 @@ export const Settings: React.FC<{
return (
<div className={[baseClass, className].filter(Boolean).join(' ')}>
<h3>{t('general:payloadSettings')}</h3>
<div className={`${baseClass}__language`}>
<Label htmlFor="language-select" label={t('general:language')} />
<ReactSelect

View File

@@ -1,6 +1,6 @@
import React from 'react'
import { Button, Form, FormSubmit, Email, MinimalTemplate } from '@payloadcms/ui'
import { Button, Form, FormSubmit, Email, MinimalTemplate, Translation } from '@payloadcms/ui'
import { SanitizedConfig } from 'payload/types'
import Link from 'next/link'
import { initPage } from '../../utilities/initPage'
@@ -25,7 +25,7 @@ export const generateMetadata = async ({
export const ForgotPassword: React.FC<{
config: Promise<SanitizedConfig>
}> = async ({ config: configPromise }) => {
const { config, user } = await initPage({ configPromise })
const { config, user, i18n } = await initPage({ configPromise })
const {
admin: { user: userSlug },
@@ -47,19 +47,19 @@ export const ForgotPassword: React.FC<{
if (user) {
return (
<MinimalTemplate className={baseClass}>
<h1>
{/* {t('alreadyLoggedIn')} */}
Already Logged In
</h1>
<h1>{i18n.t('authentication:alreadyLoggedIn')}</h1>
<p>
{/* <Trans i18nKey="loggedInChangePassword" t={t}> */}
<Link href={`${admin}/account`}>account</Link>
{/* </Trans> */}
<Translation
t={i18n.t}
i18nKey="authentication:loggedInChangePassword"
elements={{
'0': ({ children }) => <Link href={`${admin}/account`} children={children} />,
}}
/>
</p>
<br />
<Button buttonStyle="secondary" el="link" to={admin}>
Back to Dashboard
{/* {t('general:backToDashboard')} */}
{i18n.t('general:backToDashboard')}
</Button>
</MinimalTemplate>
)

View File

@@ -1,6 +1,6 @@
import { getTranslation } from '@payloadcms/translations'
import React, { Fragment, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '@payloadcms/ui'
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
import type { LivePreviewConfig } from '../../../../exports/config'
@@ -31,7 +31,7 @@ const PreviewView: React.FC<
fieldTypes: FieldTypes
}
> = (props) => {
const { i18n, t } = useTranslation('general')
const { i18n, t } = useTranslation()
const { previewWindowType } = useLivePreviewContext()
const { apiURL, data, fieldTypes, permissions } = props
@@ -69,9 +69,9 @@ const PreviewView: React.FC<
<Fragment>
{collection && (
<Meta
description={t('editing')}
description={t('general:editing')}
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
title={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
title={`${isEditing ? t('general:editing') : t('general:creating')} - ${getTranslation(
collection.labels.singular,
i18n,
)}`}
@@ -92,7 +92,7 @@ const PreviewView: React.FC<
global={global}
id={id}
isEditing={isEditing}
view={t('livePreview')}
view={t('general:livePreview')}
/>
<DocumentControls
apiURL={apiURL}

View File

@@ -8,6 +8,7 @@ import {
ConfirmPassword,
HiddenInput,
Password,
Translation,
} from '@payloadcms/ui'
import './index.scss'
import { SanitizedConfig } from 'payload/types'
@@ -62,9 +63,13 @@ export const ResetPassword: React.FC<{
{/* {t('alreadyLoggedIn')} */}
</h1>
<p>
{/* <Trans i18nKey="loginWithAnotherUser" t={t}> */}
<Link href={`${admin}${logoutRoute}`}>log out</Link>
{/* </Trans> */}
<Translation
t={i18n.t}
i18nKey="authentication:loggedInChangePassword"
elements={{
'0': ({ children }) => <Link href={`${admin}/account`} children={children} />,
}}
/>
</p>
<br />
<Button buttonStyle="secondary" el="link" to={admin}>

View File

@@ -1,7 +1,7 @@
'use client'
import qs from 'qs'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '@payloadcms/ui'
import type { PaginatedDocs } from 'payload/database'
import type { Where } from 'payload/types'
@@ -27,7 +27,7 @@ const CompareVersion: React.FC<Props> = (props) => {
const [options, setOptions] = useState(baseOptions)
const [lastLoadedPage, setLastLoadedPage] = useState(1)
const [errorLoading, setErrorLoading] = useState('')
const { i18n, t } = useTranslation('version')
const { i18n, t } = useTranslation()
const getResults = useCallback(
async ({ lastLoadedPage: lastLoadedPageArg }) => {
@@ -99,7 +99,7 @@ const CompareVersion: React.FC<Props> = (props) => {
.filter(Boolean)
.join(' ')}
>
<div className={`${baseClass}__label`}>{t('compareVersion')}</div>
<div className={`${baseClass}__label`}>{t('version:compareVersion')}</div>
{!errorLoading && (
<ReactSelect
isClearable={false}
@@ -109,7 +109,7 @@ const CompareVersion: React.FC<Props> = (props) => {
getResults({ lastLoadedPage: lastLoadedPage + 1 })
}}
options={options}
placeholder={t('selectVersionToCompare')}
placeholder={t('version:selectVersionToCompare')}
value={value}
/>
)}

View File

@@ -2,7 +2,7 @@
import React from 'react'
import { getTranslation } from '@payloadcms/translations'
import ReactDiffViewer from 'react-diff-viewer-continued'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '@payloadcms/ui'
import type { RelationshipField, SanitizedCollectionConfig } from 'payload/types'
import type { Props } from '../types'
@@ -74,12 +74,12 @@ const Relationship: React.FC<Props & { field: RelationshipField }> = ({
version,
}) => {
const { collections } = useConfig()
const { i18n, t } = useTranslation('general')
const { i18n, t } = useTranslation()
const { code: locale } = useLocale()
let placeholder = ''
if (version === comparison) placeholder = `[${t('noValue')}]`
if (version === comparison) placeholder = `[${t('general:noValue')}]`
let versionToRender = version
let comparisonToRender = comparison
@@ -102,8 +102,7 @@ const Relationship: React.FC<Props & { field: RelationshipField }> = ({
<div className={baseClass}>
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
// TODO(i18n)
{/* {getTranslation(field.label, i18n)} */}
{getTranslation(field.label, i18n)}
</Label>
<ReactDiffViewer
hideLineNumbers

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '@payloadcms/ui'
import type { Props } from './types'
@@ -9,7 +9,7 @@ import './index.scss'
const baseClass = 'select-version-locales'
const SelectLocales: React.FC<Props> = ({ onChange, options, value }) => {
const { t } = useTranslation('version')
const { t } = useTranslation()
const { code } = useLocale()
const format = (items) => {
@@ -26,12 +26,12 @@ const SelectLocales: React.FC<Props> = ({ onChange, options, value }) => {
return (
<div className={baseClass}>
<div className={`${baseClass}__label`}>{t('showLocales')}</div>
<div className={`${baseClass}__label`}>{t('version:showLocales')}</div>
<ReactSelect
isMulti
onChange={onChange}
options={format(options)}
placeholder={t('selectLocales')}
placeholder={t('version:selectLocales')}
value={format(value)}
/>
</div>

View File

@@ -118,11 +118,11 @@ export const createPayloadRequest = async ({
// need to add:
// ------------
// - files
// - transactionID
// - findByID
// - payloadDataLoader
// - payloadUploadSizes
// - files
}
const req: PayloadRequest = Object.assign(request, customRequest)

View File

@@ -19,8 +19,6 @@
},
"dependencies": {
"@faceless-ui/modal": "2.0.1",
"@payloadcms/translations": "workspace:*",
"@payloadcms/ui": "workspace:*",
"@lexical/headless": "0.12.5",
"@lexical/link": "0.12.5",
"@lexical/list": "0.12.5",
@@ -30,16 +28,16 @@
"@lexical/rich-text": "0.12.5",
"@lexical/selection": "0.12.5",
"@lexical/utils": "0.12.5",
"@payloadcms/translations": "workspace:*",
"@payloadcms/ui": "workspace:*",
"bson-objectid": "2.0.4",
"classnames": "^2.3.2",
"deep-equal": "2.2.3",
"i18next": "22.5.1",
"lexical": "0.12.5",
"lodash": "4.17.21",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.11",
"react-i18next": "11.18.6",
"ts-essentials": "7.0.3"
},
"devDependencies": {

View File

@@ -13,11 +13,11 @@ import {
createNestedFieldPath,
useDocumentInfo,
useFormSubmitted,
useTranslation,
} from '@payloadcms/ui'
import isDeepEqual from 'deep-equal'
import { $getNodeByKey } from 'lexical'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import type { FieldProps } from '../../../../types'
import type { BlockFields, BlockNode } from '../nodes/BlocksNode'
@@ -181,8 +181,9 @@ export const BlockContent: React.FC<Props> = (props) => {
className={`${baseClass}__block-pill ${baseClass}__block-pill-${formData?.blockType}`}
pillStyle="white"
>
{typeof labels.singular === 'string' ? labels.singular : '[Singular Label]'}
{/* {getTranslation(labels.singular, i18n)} */}
{typeof labels.singular === 'string'
? getTranslation(labels.singular, i18n)
: '[Singular Label]'}
</Pill>
<SectionTitle path={`${path}blockName`} readOnly={field?.admin?.readOnly} />
{fieldHasErrors && <ErrorPill count={errorCount} withMessage />}

View File

@@ -7,6 +7,7 @@ import {
useDocumentInfo,
useFormSubmitted,
useLocale,
useTranslation,
} from '@payloadcms/ui'
import React, { useEffect, useMemo } from 'react'
@@ -16,7 +17,6 @@ const baseClass = 'lexical-block'
import type { Data } from 'payload/types'
import { sanitizeFields } from 'payload/config'
import { useTranslation } from 'react-i18next'
import type { BlocksFeatureProps } from '..'
@@ -63,7 +63,7 @@ export const BlockComponent: React.FC<Props> = (props) => {
const initialStateRef = React.useRef<Data>(null) // Store initial value in a ref, so it doesn't change on re-render and only gets initialized once
const config = useConfig()
const { t } = useTranslation('general')
const { t } = useTranslation()
const { code: locale } = useLocale()
const { getDocPreferences } = useDocumentInfo()

View File

@@ -1,7 +1,7 @@
'use client'
import { useModal } from '@faceless-ui/modal'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { BlocksDrawer, formatDrawerSlug, useEditDepth } from '@payloadcms/ui'
import { BlocksDrawer, formatDrawerSlug, useEditDepth, useTranslation } from '@payloadcms/ui'
import {
$getNodeByKey,
COMMAND_PRIORITY_EDITOR,
@@ -10,7 +10,6 @@ import {
createCommand,
} from 'lexical'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { BlocksFeatureProps } from '..'
@@ -60,12 +59,12 @@ export const BlocksDrawerComponent: React.FC = () => {
const [replaceNodeKey, setReplaceNodeKey] = useState<null | string>(null)
const editDepth = useEditDepth()
const { t } = useTranslation('fields')
const { t } = useTranslation()
const { openModal } = useModal()
const labels = {
plural: t('blocks') || 'Blocks',
singular: t('block') || 'Block',
plural: t('fields:blocks') || 'Blocks',
singular: t('fields:block') || 'Block',
}
const addRow = useCallback(

View File

@@ -1,6 +1,5 @@
import { Drawer, Form, FormSubmit, RenderFields, fieldTypes } from '@payloadcms/ui'
import { Drawer, Form, FormSubmit, RenderFields, fieldTypes, useTranslation } from '@payloadcms/ui'
import React from 'react'
import { useTranslation } from 'react-i18next'
import './index.scss'
import { type Props } from './types'
@@ -13,10 +12,10 @@ export const LinkDrawer: React.FC<Props> = ({
handleModalSubmit,
initialState,
}) => {
const { t } = useTranslation('fields')
const { t } = useTranslation()
return (
<Drawer className={baseClass} slug={drawerSlug} title={t('editLink') ?? ''}>
<Drawer className={baseClass} slug={drawerSlug} title={t('fields:editLink') ?? ''}>
<Form fields={fieldSchema} initialState={initialState} onSubmit={handleModalSubmit}>
[RenderFields]
{/* <RenderFields

View File

@@ -14,6 +14,7 @@ import {
useDocumentInfo,
useEditDepth,
useLocale,
useTranslation,
} from '@payloadcms/ui'
import {
$getSelection,
@@ -25,7 +26,6 @@ import {
} from 'lexical'
import { sanitizeFields } from 'payload/config'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { LinkFeatureProps } from '../../..'
import type { LinkNode } from '../../../nodes/LinkNode'
@@ -57,7 +57,7 @@ export function LinkEditor({
const { user } = useAuth()
const { code: locale } = useLocale()
const { i18n, t } = useTranslation(['fields', 'upload', 'general'])
const { i18n, t } = useTranslation()
const { getDocPreferences } = useDocumentInfo()
@@ -137,8 +137,6 @@ export function LinkEditor({
(coll) => coll.slug === linkParent.getFields()?.doc?.relationTo,
)
const label = t('fields:linkedTo', {
// TODO: fix this
// @ts-ignore-next-line
label: getTranslation(relatedField.labels.singular, i18n),
}).replace(/<[^>]*>?/g, '')
setLinkLabel(label)

View File

@@ -2,10 +2,9 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
import { getTranslation } from '@payloadcms/translations'
import { Button, useConfig, useDocumentDrawer, usePayloadAPI } from '@payloadcms/ui'
import { Button, useConfig, useDocumentDrawer, usePayloadAPI, useTranslation } from '@payloadcms/ui'
import { $getNodeByKey, type ElementFormatType } from 'lexical'
import React, { useCallback, useReducer, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { RelationshipData } from '../RelationshipNode'
@@ -50,7 +49,7 @@ const Component: React.FC<Props> = (props) => {
collections.find((coll) => coll.slug === relationTo),
)
const { i18n, t } = useTranslation(['fields', 'general'])
const { i18n, t } = useTranslation()
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
const [{ data }, { setParams }] = usePayloadAPI(
`${serverURL}${api}/${relatedCollection.slug}/${id}`,
@@ -88,9 +87,7 @@ const Component: React.FC<Props> = (props) => {
>
<div className={`${baseClass}__wrap`}>
<p className={`${baseClass}__label`}>
{t('labelRelationship', {
// TODO: fix this
// @ts-ignore-next-line
{t('fields:labelRelationship', {
label: getTranslation(relatedCollection.labels.singular, i18n),
})}
</p>
@@ -114,7 +111,7 @@ const Component: React.FC<Props> = (props) => {
})
}}
round
tooltip={t('swapRelationship')}
tooltip={t('fields:swapRelationship')}
/>
<Button
buttonStyle="icon-label"

View File

@@ -14,12 +14,12 @@ import {
useConfig,
useDocumentInfo,
useLocale,
useTranslation,
} from '@payloadcms/ui'
import { $getNodeByKey } from 'lexical'
import { sanitizeFields } from 'payload/config'
import { deepCopyObject } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { ElementProps } from '..'
import type { UploadFeatureProps } from '../..'
@@ -122,8 +122,6 @@ export const ExtraFieldsUploadDrawer: React.FC<
<Drawer
slug={drawerSlug}
title={t('general:editLabel', {
// TODO: fix this
// @ts-ignore-next-line
label: getTranslation(relatedCollection.labels.singular, i18n),
})}
>

View File

@@ -13,10 +13,10 @@ import {
useDrawerSlug,
usePayloadAPI,
useThumbnail,
useTranslation,
} from '@payloadcms/ui'
import { $getNodeByKey } from 'lexical'
import React, { useCallback, useReducer, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { UploadFeatureProps } from '..'
import type { UploadData } from '../nodes/UploadNode'
@@ -55,7 +55,7 @@ const Component: React.FC<ElementProps> = (props) => {
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey)
const { editorConfig, field } = useEditorConfigContext()
const { i18n, t } = useTranslation('fields')
const { i18n, t } = useTranslation()
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
const [relatedCollection, setRelatedCollection] = useState<SanitizedCollectionConfig>(() =>
collections.find((coll) => coll.slug === relationTo),
@@ -147,7 +147,7 @@ const Component: React.FC<ElementProps> = (props) => {
})
}}
round
tooltip={t('swapUpload')}
tooltip={t('fields:swapUpload')}
/>
<Button
buttonStyle="icon-label"
@@ -159,7 +159,7 @@ const Component: React.FC<ElementProps> = (props) => {
removeUpload()
}}
round
tooltip={t('removeUpload')}
tooltip={t('fields:removeUpload')}
/>
</div>
)}

View File

@@ -1,4 +1,4 @@
import type { i18n } from 'i18next'
import type { I18n } from '@payloadcms/translations'
import type { LexicalEditor } from 'lexical'
import type { MutableRefObject } from 'react'
import type React from 'react'
@@ -7,7 +7,7 @@ export class SlashMenuOption {
// Icon for display
Icon: () => Promise<React.FC>
displayName?: (({ i18n }: { i18n: i18n }) => string) | string
displayName?: (({ i18n }: { i18n: I18n }) => string) | string
// Used for class names and, if displayName is not provided, for display.
key: string
// TBD
@@ -23,7 +23,7 @@ export class SlashMenuOption {
key: string,
options: {
Icon: () => Promise<React.FC>
displayName?: (({ i18n }: { i18n: i18n }) => string) | string
displayName?: (({ i18n }: { i18n: I18n }) => string) | string
keyboardShortcut?: string
keywords?: Array<string>
onSelect: ({ editor, queryString }: { editor: LexicalEditor; queryString: string }) => void
@@ -47,7 +47,7 @@ export class SlashMenuOption {
export class SlashMenuGroup {
// Used for class names and, if displayName is not provided, for display.
displayName?: (({ i18n }: { i18n: i18n }) => string) | string
displayName?: (({ i18n }: { i18n: I18n }) => string) | string
key: string
options: Array<SlashMenuOption>
}

View File

@@ -2,10 +2,10 @@
import type { TextNode } from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useTranslation } from '@payloadcms/ui'
import { useCallback, useMemo, useState } from 'react'
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import { useTranslation } from 'react-i18next'
import type { SlashMenuGroup, SlashMenuOption } from './LexicalTypeaheadMenuPlugin/types'
@@ -28,7 +28,7 @@ function SlashMenuItem({
onMouseEnter: () => void
option: SlashMenuOption
}) {
const { i18n } = useTranslation('fields')
const { i18n } = useTranslation()
let className = `${baseClass}__item ${baseClass}__item-${option.key}`
if (isSelected) {
@@ -87,7 +87,7 @@ export function SlashMenuPlugin({
const [editor] = useLexicalComposerContext()
const [queryString, setQueryString] = useState<null | string>(null)
const { editorConfig } = useEditorConfigContext()
const { i18n } = useTranslation('fields')
const { i18n } = useTranslation()
const checkForTriggerMatch = useMenuTriggerMatch('/', {
minLength: 0,

View File

@@ -18,10 +18,10 @@
},
"dependencies": {
"@faceless-ui/modal": "2.0.1",
"i18next": "22.5.1",
"@payloadcms/translations": "workspace:^",
"@payloadcms/ui": "workspace:^",
"is-hotkey": "0.2.0",
"react": "18.2.0",
"react-i18next": "11.18.6",
"slate": "0.91.4",
"slate-history": "0.86.0",
"slate-hyperscript": "0.81.3",

View File

@@ -4,12 +4,17 @@ import type { BaseEditor, BaseOperation } from 'slate'
import type { HistoryEditor } from 'slate-history'
import type { ReactEditor } from 'slate-react'
import { getTranslation } from '@payloadcms/translations'
import {
Error,
FieldDescription,
Label,
useEditDepth,
useField,
useTranslation,
} from '@payloadcms/ui'
import isHotkey from 'is-hotkey'
import { Error, FieldDescription, Label, useField, withCondition } from 'payload/components/forms'
import { useEditDepth } from 'payload/components/utilities'
import { getTranslation } from 'payload/utilities'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Node, Element as SlateElement, Text, Transforms, createEditor } from 'slate'
import { withHistory } from 'slate-history'
import { Editable, Slate, withReact } from 'slate-react'

View File

@@ -1,20 +1,20 @@
'use client'
import type { Fields } from 'payload/types'
import type { Fields } from '@payloadcms/ui'
import { useModal } from '@faceless-ui/modal'
import { useDrawerSlug } from 'payload/components/elements'
import { reduceFieldsToValues } from 'payload/components/forms'
import {
buildStateFromSchema,
reduceFieldsToValues,
useAuth,
useConfig,
useDocumentInfo,
useDrawerSlug,
useLocale,
} from 'payload/components/utilities'
useTranslation,
} from '@payloadcms/ui'
import { sanitizeFields } from 'payload/config'
import React, { Fragment, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Editor, Range, Transforms } from 'slate'
import { ReactEditor, useSlate } from 'slate-react'
@@ -73,7 +73,7 @@ export const LinkButton: React.FC<{
const { code: locale } = useLocale()
const [initialState, setInitialState] = useState<Fields>({})
const { i18n, t } = useTranslation(['upload', 'general'])
const { i18n, t } = useTranslation()
const editor = useSlate()
const config = useConfig()

View File

@@ -1,23 +1,26 @@
'use client'
import type { Fields } from 'payload/types'
import type { Fields } from '@payloadcms/ui'
import type { HTMLAttributes } from 'react'
import { useModal } from '@faceless-ui/modal'
import { Button, Popup } from 'payload/components'
import { useDrawerSlug } from 'payload/components/elements'
import { reduceFieldsToValues } from 'payload/components/forms'
import { getTranslation } from '@payloadcms/translations'
import {
Button,
Popup,
Translation,
buildStateFromSchema,
reduceFieldsToValues,
useAuth,
useConfig,
useDocumentInfo,
useDrawerSlug,
useLocale,
} from 'payload/components/utilities'
useTranslation,
} from '@payloadcms/ui'
import { sanitizeFields } from 'payload/config'
import { deepCopyObject, getTranslation } from 'payload/utilities'
import { deepCopyObject } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Editor, Node, Transforms } from 'slate'
import { ReactEditor, useSlate } from 'slate-react'
@@ -73,7 +76,7 @@ export const LinkElement: React.FC<{
const config = useConfig()
const { user } = useAuth()
const { code: locale } = useLocale()
const { i18n, t } = useTranslation('fields')
const { i18n, t } = useTranslation()
const { closeModal, openModal, toggleModal } = useModal()
const [renderModal, setRenderModal] = useState(false)
const [renderPopup, setRenderPopup] = useState(false)
@@ -155,16 +158,9 @@ export const LinkElement: React.FC<{
render={() => (
<div className={`${baseClass}__popup`}>
{element.linkType === 'internal' && element.doc?.relationTo && element.doc?.value && (
<Trans
i18nKey="fields:linkedTo"
values={{
label: getTranslation(
config.collections.find(({ slug }) => slug === element.doc.relationTo)?.labels
?.singular,
i18n,
),
}}
>
<Translation
elements={{
'0': ({ children }) => (
<a
className={`${baseClass}__link-label`}
href={`${config.routes.admin}/collections/${element.doc.relationTo}/${element.doc.value}`}
@@ -172,9 +168,20 @@ export const LinkElement: React.FC<{
target="_blank"
title={`${config.routes.admin}/collections/${element.doc.relationTo}/${element.doc.value}`}
>
label
{children}
</a>
</Trans>
),
}}
i18nKey="fields:linkedTo"
t={t}
variables={{
label: getTranslation(
config.collections.find(({ slug }) => slug === element.doc.relationTo)?.labels
?.singular,
i18n,
),
}}
/>
)}
{(element.linkType === 'custom' || !element.linkType) && (
<a

View File

@@ -1,12 +1,16 @@
'use client'
import { Drawer } from 'payload/components/elements'
import { Form, FormSubmit, RenderFields } from 'payload/components/forms'
import { fieldTypes } from 'payload/components/forms'
import {
Drawer,
Form,
FormSubmit,
RenderFields,
fieldTypes,
useEditDepth,
useTranslation,
} from '@payloadcms/ui'
import { useHotkey } from 'payload/components/hooks'
import { useEditDepth } from 'payload/components/utilities'
import React, { useRef } from 'react'
import { useTranslation } from 'react-i18next'
import type { Props } from './types'
@@ -20,10 +24,10 @@ export const LinkDrawer: React.FC<Props> = ({
handleModalSubmit,
initialState,
}) => {
const { t } = useTranslation('fields')
const { t } = useTranslation()
return (
<Drawer className={baseClass} slug={drawerSlug} title={t('editLink')}>
<Drawer className={baseClass} slug={drawerSlug} title={t('fields:editLink')}>
<Form fields={fieldSchema} initialState={initialState} onSubmit={handleModalSubmit}>
<RenderFields
fieldSchema={fieldSchema}
@@ -38,7 +42,7 @@ export const LinkDrawer: React.FC<Props> = ({
}
const LinkSubmit: React.FC = () => {
const { t } = useTranslation('fields')
const { t } = useTranslation()
const ref = useRef<HTMLButtonElement>(null)
const editDepth = useEditDepth()

View File

@@ -1,11 +1,14 @@
'use client'
import { RelationshipComponent } from 'payload/components/fields/Relationship'
import { SelectComponent } from 'payload/components/fields/Select'
import { useFormFields } from 'payload/components/forms'
import { useAuth, useConfig } from 'payload/components/utilities'
import {
RelationshipComponent,
SelectComponent,
useAuth,
useConfig,
useFormFields,
useTranslation,
} from '@payloadcms/ui'
import React, { Fragment, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
const createOptions = (collections, permissions) =>
collections.reduce((options, collection) => {
@@ -28,7 +31,7 @@ const createOptions = (collections, permissions) =>
const RelationshipFields = () => {
const { collections } = useConfig()
const { permissions } = useAuth()
const { t } = useTranslation('fields')
const { t } = useTranslation()
const [options, setOptions] = useState(() => createOptions(collections, permissions))
const relationTo = useFormFields<string>(([fields]) => fields.relationTo?.value as string)
@@ -39,10 +42,15 @@ const RelationshipFields = () => {
return (
<Fragment>
<SelectComponent label={t('relationTo')} name="relationTo" options={options} required />
<SelectComponent
label={t('fields:relationTo')}
name="relationTo"
options={options}
required
/>
{relationTo && (
<RelationshipComponent
label={t('relatedDocument')}
label={t('fields:relatedDocument')}
name="value"
relationTo={relationTo}
required

View File

@@ -1,8 +1,7 @@
'use client'
import { useListDrawer } from 'payload/components/elements'
import { useListDrawer, useTranslation } from '@payloadcms/ui'
import React, { Fragment, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ReactEditor, useSlate } from 'slate-react'
import RelationshipIcon from '../../../icons/Relationship'
@@ -33,7 +32,7 @@ type Props = {
path: string
}
const RelationshipButton: React.FC<Props> = ({ enabledCollectionSlugs }) => {
const { t } = useTranslation('fields')
const { t } = useTranslation()
const editor = useSlate()
const [selectedCollectionSlug, setSelectedCollectionSlug] = useState(
() => enabledCollectionSlugs[0],
@@ -72,7 +71,7 @@ const RelationshipButton: React.FC<Props> = ({ enabledCollectionSlugs }) => {
onClick={() => {
// do nothing
}}
tooltip={t('addRelationship')}
tooltip={t('fields:addRelationship')}
>
<RelationshipIcon />
</ElementButton>

View File

@@ -2,13 +2,16 @@
import type { HTMLAttributes } from 'react'
import { Button } from 'payload/components'
import { useDocumentDrawer, useListDrawer } from 'payload/components/elements'
import { usePayloadAPI } from 'payload/components/hooks'
import { useConfig } from 'payload/components/utilities'
import { getTranslation } from 'payload/utilities'
import { getTranslation } from '@payloadcms/translations'
import {
Button,
useConfig,
useDocumentDrawer,
useListDrawer,
usePayloadAPI,
useTranslation,
} from '@payloadcms/ui'
import React, { useCallback, useReducer, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Transforms } from 'slate'
import { ReactEditor, useFocused, useSelected, useSlateStatic } from 'slate-react'
@@ -53,7 +56,7 @@ const Element: React.FC<Props> = (props) => {
)
const selected = useSelected()
const focused = useFocused()
const { i18n, t } = useTranslation(['fields', 'general'])
const { i18n, t } = useTranslation()
const editor = useSlateStatic()
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
const [{ data }, { setParams }] = usePayloadAPI(
@@ -141,7 +144,7 @@ const Element: React.FC<Props> = (props) => {
>
<div className={`${baseClass}__wrap`}>
<p className={`${baseClass}__label`}>
{t('labelRelationship', {
{t('fields:labelRelationship', {
label: getTranslation(relatedCollection.labels.singular, i18n),
})}
</p>
@@ -165,7 +168,7 @@ const Element: React.FC<Props> = (props) => {
// do nothing
}}
round
tooltip={t('swapRelationship')}
tooltip={t('fields:swapRelationship')}
/>
</ListDrawerToggler>
<Button

View File

@@ -1,8 +1,7 @@
'use client'
import { useListDrawer } from 'payload/components/elements'
import { useListDrawer, useTranslation } from '@payloadcms/ui'
import React, { Fragment, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { ReactEditor, useSlate } from 'slate-react'
import UploadIcon from '../../../icons/Upload'
@@ -34,7 +33,7 @@ type ButtonProps = {
}
const UploadButton: React.FC<ButtonProps> = ({ enabledCollectionSlugs }) => {
const { t } = useTranslation(['upload', 'general'])
const { t } = useTranslation()
const editor = useSlate()
const [ListDrawer, ListDrawerToggler, { closeDrawer }] = useListDrawer({

View File

@@ -3,19 +3,23 @@
import type { SanitizedCollectionConfig } from 'payload/types'
import { useModal } from '@faceless-ui/modal'
import { Drawer } from 'payload/components/elements'
import { Form, FormSubmit, RenderFields, fieldTypes } from 'payload/components/forms'
import { getTranslation } from '@payloadcms/translations'
import {
Drawer,
Form,
FormSubmit,
RenderFields,
buildStateFromSchema,
fieldTypes,
useAuth,
useConfig,
useDocumentInfo,
useLocale,
} from 'payload/components/utilities'
useTranslation,
} from '@payloadcms/ui'
import { sanitizeFields } from 'payload/config'
import { deepCopyObject, getTranslation } from 'payload/utilities'
import { deepCopyObject } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Transforms } from 'slate'
import { ReactEditor, useSlateStatic } from 'slate-react'

View File

@@ -3,19 +3,20 @@
import type { SanitizedCollectionConfig } from 'payload/types'
import type { HTMLAttributes } from 'react'
import { Button } from 'payload/components'
import { getTranslation } from '@payloadcms/translations'
import {
Button,
DrawerToggler,
FileGraphic,
useConfig,
useDocumentDrawer,
useDrawerSlug,
useListDrawer,
} from 'payload/components/elements'
import { FileGraphic } from 'payload/components/graphics'
import { usePayloadAPI, useThumbnail } from 'payload/components/hooks'
import { useConfig } from 'payload/components/utilities'
import { getTranslation } from 'payload/utilities'
usePayloadAPI,
useThumbnail,
useTranslation,
} from '@payloadcms/ui'
import React, { useCallback, useReducer, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Transforms } from 'slate'
import { ReactEditor, useFocused, useSelected, useSlateStatic } from 'slate-react'
@@ -54,7 +55,7 @@ const Element: React.FC<ElementProps> = (props) => {
routes: { api },
serverURL,
} = useConfig()
const { i18n, t } = useTranslation('fields')
const { i18n, t } = useTranslation()
const [cacheBust, dispatchCacheBust] = useReducer((state) => state + 1, 0)
const [relatedCollection, setRelatedCollection] = useState<SanitizedCollectionConfig>(() =>
collections.find((coll) => coll.slug === relationTo),
@@ -187,7 +188,7 @@ const Element: React.FC<ElementProps> = (props) => {
// do nothing
}}
round
tooltip={t('swapUpload')}
tooltip={t('fields:swapUpload')}
/>
</ListDrawerToggler>
<Button
@@ -200,7 +201,7 @@ const Element: React.FC<ElementProps> = (props) => {
removeUpload()
}}
round
tooltip={t('removeUpload')}
tooltip={t('fields:removeUpload')}
/>
</div>
</div>

View File

@@ -135,6 +135,7 @@ const clientTranslationKeys = [
'fields:searchForBlock',
'fields:selectFieldsToEdit',
'fields:showAll',
'fields:swapRelationship',
'fields:uploadNewLabel',
'general:aboutToDeleteCount',

View File

@@ -50,7 +50,11 @@ export const getTranslationString = ({
return undefined
}, translations)
return translation
if (!translation) {
console.log('key not found: ', key)
}
return translation || key
}
/**
@@ -65,14 +69,22 @@ const replaceVars = ({
vars,
}: {
translationString: string
vars: Record<string, string>
}) => {
return Object.keys(vars).reduce((acc, varKey) => {
if (acc) {
return acc.replace(`{{${varKey}}}`, vars[varKey])
vars: {
[key: string]: any
}
return acc
}, translationString)
}) => {
const parts = translationString.split(/({{.*?}})/)
return parts
.map((part) => {
if (part.startsWith('{{') && part.endsWith('}}')) {
const placeholder = part.substring(2, part.length - 2).trim()
return vars[placeholder] || part
} else {
return part
}
})
.join('')
}
/**

View File

@@ -16,6 +16,7 @@ import { Button } from '../Button'
import * as PopupList from '../Popup/PopupButtonList'
import './index.scss'
import { useRouter } from 'next/navigation'
import { Translation } from '../Translation'
const baseClass = 'delete-document'
@@ -112,18 +113,17 @@ const DeleteDocument: React.FC<Props> = (props) => {
<MinimalTemplate className={`${baseClass}__template`}>
<h1>{t('general:confirmDeletion')}</h1>
<p>
{t('general:aboutToDelete', {
<Translation
t={t}
i18nKey="general:aboutToDelete"
variables={{
label: getTranslation(singularLabel, i18n),
title: titleToRender,
})}
{/* <Trans
i18nKey="aboutToDelete"
t={t}
values={{ label: getTranslation(singularLabel, i18n), title: titleToRender }}
>
aboutToDelete
<strong>{titleToRender}</strong>
</Trans> */}
}}
elements={{
'1': ({ children }) => <strong children={children} />,
}}
/>
</p>
<div className={`${baseClass}__actions`}>
<Button

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '../../providers/Translation'
import { Button } from '../Button'
import './index.scss'
@@ -22,7 +22,7 @@ export const Dropzone: React.FC<Props> = ({ onChange, className, mimeTypes }) =>
const [dragging, setDragging] = React.useState(false)
const inputRef = React.useRef(null)
const { t } = useTranslation(['upload', 'general'])
const { t } = useTranslation()
const handlePaste = React.useCallback(
(e: ClipboardEvent) => {
@@ -107,7 +107,7 @@ export const Dropzone: React.FC<Props> = ({ onChange, className, mimeTypes }) =>
}}
className={`${baseClass}__file-button`}
>
{t('selectFile')}
{t('upload:selectFile')}
</Button>
<input
@@ -119,7 +119,7 @@ export const Dropzone: React.FC<Props> = ({ onChange, className, mimeTypes }) =>
/>
<p className={`${baseClass}__label`}>
{t('general:or')} {t('dragAndDrop')}
{t('general:or')} {t('upload:dragAndDrop')}
</p>
</div>
)

View File

@@ -1,6 +1,6 @@
import { useModal } from '@faceless-ui/modal'
import React, { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '../../providers/Translation'
import ReactCrop, { type Crop as CropType } from 'react-image-crop'
import 'react-image-crop/dist/ReactCrop.css'
@@ -34,7 +34,7 @@ export const EditUpload: React.FC<{
showFocalPoint?: boolean
}> = ({ fileName, fileSrc, imageCacheTag, showCrop, showFocalPoint }) => {
const { closeModal } = useModal()
const { t } = useTranslation(['general', 'upload'])
const { t } = useTranslation()
const { formQueryParams, setFormQueryParams } = useFormQueryParams()
const { uploadEdits } = formQueryParams || {}
const [crop, setCrop] = useState<CropType>({
@@ -117,7 +117,7 @@ export const EditUpload: React.FC<{
</h2>
<div className={`${baseClass}__actions`}>
<Button
aria-label={t('cancel')}
aria-label={t('general:cancel')}
buttonStyle="secondary"
className={`${baseClass}__cancel`}
onClick={() => closeModal(editDrawerSlug)}

View File

@@ -9,6 +9,7 @@ import { MinimalTemplate } from '../../templates/Minimal'
import { useDocumentInfo } from '../../providers/DocumentInfo'
import { Button } from '../Button'
import './index.scss'
import { Translation } from '../Translation'
const baseClass = 'generate-confirmation'
@@ -43,11 +44,13 @@ const GenerateConfirmation: React.FC<Props> = (props) => {
<MinimalTemplate className={`${baseClass}__template`}>
<h1>{t('authentication:confirmGeneration')}</h1>
<p>
<strong>{t('authentication:generatingNewAPIKeyWillInvalidate')}</strong>
{/* <Trans i18nKey="generatingNewAPIKeyWillInvalidate" t={t}>
generatingNewAPIKeyWillInvalidate
<strong>invalidate</strong>
</Trans> */}
<Translation
t={t}
i18nKey="authentication:generatingNewAPIKeyWillInvalidate"
elements={{
1: ({ children }) => <strong children={children} />,
}}
/>
</p>
<Button

View File

@@ -1,6 +1,6 @@
import { Modal, useModal } from '@faceless-ui/modal'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '../../providers/Translation'
import { useHistory } from 'react-router-dom'
import type { Props } from './types'
@@ -22,14 +22,14 @@ const StayLoggedInModal: React.FC<Props> = (props) => {
routes: { admin },
} = config
const { toggleModal } = useModal()
const { t } = useTranslation('authentication')
const { t } = useTranslation()
return (
<Modal className={baseClass} slug="stay-logged-in">
<div className={`${baseClass}__wrapper`}>
<div className={`${baseClass}__content`}>
<h1>{t('stayLoggedIn')}</h1>
<p>{t('youAreInactive')}</p>
<h1>{t('authentication:stayLoggedIn')}</h1>
<p>{t('authentication:youAreInactive')}</p>
</div>
<div className={`${baseClass}__controls`}>
<Button
@@ -39,7 +39,7 @@ const StayLoggedInModal: React.FC<Props> = (props) => {
history.push(`${admin}${logoutRoute}`)
}}
>
{t('logOut')}
{t('authentication:logOut')}
</Button>
<Button
onClick={() => {
@@ -47,7 +47,7 @@ const StayLoggedInModal: React.FC<Props> = (props) => {
toggleModal(modalSlug)
}}
>
{t('stayLoggedIn')}
{t('authentication:stayLoggedIn')}
</Button>
</div>
</div>

View File

@@ -0,0 +1,44 @@
import * as React from 'react'
import { TFunction } from '@payloadcms/translations'
const RecursiveTranslation: React.FC<{
translationString: string
elements?: Record<string, React.FC<{ children: React.ReactNode }>>
}> = ({ translationString, elements }) => {
const regex = /(<[^>]+>.*?<\/[^>]+>)/g
const sections = translationString.split(regex)
return (
<span>
{sections.map((section, index) => {
const Element = elements?.[index.toString()]
if (Element) {
const regex = new RegExp(`<${index}>(.*?)<\/${index}>`, 'g')
const children = section.replace(regex, (_, group) => group)
return (
<Element>
<RecursiveTranslation translationString={children} />
</Element>
)
}
return section
})}
</span>
)
}
type TranslationProps = {
i18nKey: string
variables?: Record<string, unknown>
elements?: Record<string, React.FC<{ children: React.ReactNode }>>
t: TFunction
}
export const Translation: React.FC<TranslationProps> = ({ elements, variables, i18nKey, t }) => {
let stringWithVariables = t(i18nKey, variables || {})
if (!elements) {
return stringWithVariables
}
return <RecursiveTranslation translationString={stringWithVariables} elements={elements} />
}

View File

@@ -32,4 +32,6 @@ export { useListDrawer } from '../elements/ListDrawer'
export { useDocumentDrawer } from '../elements/DocumentDrawer'
export { ShimmerEffect } from '../elements/ShimmerEffect'
export { useDrawerSlug } from '../elements/Drawer/useDrawerSlug'
export { default as Popup } from '../elements/Popup'
// export { useThumbnail } from '../elements/Upload'
export { Translation } from '../elements/Translation'

View File

@@ -3,7 +3,7 @@ import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/t
import { useFormFields } from '../forms/Form/context'
import { formatDocTitle } from '../utilities/formatDocTitle'
import { useTranslation } from 'react-i18next'
import { useTranslation } from '../providers/Translation'
import { getTranslation } from '@payloadcms/translations'
const useTitle = (args: {

View File

@@ -11,15 +11,15 @@ export type LanguageOptions = {
}[]
const Context = createContext<{
t: (key: string, vars?: Record<string, string | number>) => string
t: (key: string, vars?: Record<string, any>) => string
i18n: I18n
languageOptions: LanguageOptions
}>({
t: () => '',
t: (key) => key,
i18n: {
language: 'en',
fallbackLanguage: 'en',
t: () => '',
t: (key) => key,
},
languageOptions: undefined,
})
@@ -36,7 +36,7 @@ export const TranslationProvider: React.FC<{
fallbackLang: ClientConfig['i18n']['fallbackLanguage']
languageOptions: LanguageOptions
}> = ({ children, translations, lang, fallbackLang, languageOptions }) => {
const nextT = (key: string, vars?: Record<string, string | number>): string =>
const nextT = (key: string, vars?: Record<string, any>): string =>
t({
key,
vars,

View File

@@ -13,7 +13,7 @@ jest.mock('../../../../utilities/Config', () => ({
useConfig: () => ({ admin: { dateFormat: 'MMMM do yyyy, h:mm a' } }),
}))
jest.mock('react-i18next', () => ({
jest.mock('../../../../providers/Translation', () => ({
useTranslation: () => ({ t: (string) => string }),
}))

18
pnpm-lock.yaml generated
View File

@@ -1245,9 +1245,6 @@ importers:
deep-equal:
specifier: 2.2.3
version: 2.2.3
i18next:
specifier: 22.5.1
version: 22.5.1
lexical:
specifier: 0.12.5
version: 0.12.5
@@ -1263,9 +1260,6 @@ importers:
react-error-boundary:
specifier: ^4.0.11
version: 4.0.11(react@18.2.0)
react-i18next:
specifier: 11.18.6
version: 11.18.6(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0)
ts-essentials:
specifier: 7.0.3
version: 7.0.3(typescript@5.2.2)
@@ -1288,18 +1282,18 @@ importers:
'@faceless-ui/modal':
specifier: 2.0.1
version: 2.0.1(react-dom@18.2.0)(react@18.2.0)
i18next:
specifier: 22.5.1
version: 22.5.1
'@payloadcms/translations':
specifier: workspace:^
version: link:../translations
'@payloadcms/ui':
specifier: workspace:^
version: link:../ui
is-hotkey:
specifier: 0.2.0
version: 0.2.0
react:
specifier: 18.2.0
version: 18.2.0
react-i18next:
specifier: 11.18.6
version: 11.18.6(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0)
slate:
specifier: 0.91.4
version: 0.91.4