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

@@ -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', {
label: getTranslation(singularLabel, i18n),
title: titleToRender,
})}
{/* <Trans
i18nKey="aboutToDelete"
<Translation
t={t}
values={{ label: getTranslation(singularLabel, i18n), title: titleToRender }}
>
aboutToDelete
<strong>{titleToRender}</strong>
</Trans> */}
i18nKey="general:aboutToDelete"
variables={{
label: getTranslation(singularLabel, i18n),
title: titleToRender,
}}
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 }),
}))