feat: add internationalization (i18n) to locales (#4005)
This commit is contained in:
committed by
GitHub
parent
57da3c99a7
commit
6a0a859563
@@ -57,6 +57,38 @@ export default buildConfig({
|
||||
})
|
||||
```
|
||||
|
||||
**Example Payload config set up for localization with full locales objects (including [internationalization](/docs/configuration/i18n) support):**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
// collections go here
|
||||
],
|
||||
localization: {
|
||||
locales: [
|
||||
{
|
||||
label: {
|
||||
en: 'English', // English label
|
||||
nb: 'Engelsk', // Norwegian label
|
||||
},
|
||||
code: 'en',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'Norwegian', // English label
|
||||
nb: 'Norsk', // Norwegian label
|
||||
},
|
||||
code: 'nb',
|
||||
},
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Here is a brief explanation of each of the options available within the `localization` property:**
|
||||
|
||||
**`locales`**
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import React from 'react'
|
||||
import { Chevron } from '../../..'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Chevron } from '../../..'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'localizer-button'
|
||||
|
||||
export const LocalizerLabel: React.FC<{
|
||||
className?: string
|
||||
ariaLabel?: string
|
||||
className?: string
|
||||
}> = (props) => {
|
||||
const { className, ariaLabel } = props
|
||||
const { ariaLabel, className } = props
|
||||
const locale = useLocale()
|
||||
const { t } = useTranslation('general')
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
aria-label={ariaLabel || t('locale')}
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__label`}>{`${t('locale')}:`}</div>
|
||||
|
||||
<span className={`${baseClass}__current-label`}>{`${locale.label}`}</span>
|
||||
<span className={`${baseClass}__current-label`}>{`${getTranslation(
|
||||
locale.label,
|
||||
i18n,
|
||||
)}`}</span>
|
||||
|
||||
<Chevron className={`${baseClass}__chevron`} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import qs from 'qs'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import { useSearchParams } from '../../utilities/SearchParams'
|
||||
@@ -18,9 +20,12 @@ const Localizer: React.FC<{
|
||||
const config = useConfig()
|
||||
const { localization } = config
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const localeLabel = getTranslation(locale.label, i18n)
|
||||
|
||||
if (localization) {
|
||||
const { locales } = localization
|
||||
|
||||
@@ -44,8 +49,8 @@ const Localizer: React.FC<{
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{locale.label}
|
||||
{locale.label !== locale.code && ` (${locale.code})`}
|
||||
{localeLabel}
|
||||
{localeLabel !== locale.code && ` (${locale.code})`}
|
||||
</PopupList.Button>
|
||||
) : null}
|
||||
|
||||
@@ -57,11 +62,12 @@ const Localizer: React.FC<{
|
||||
locale: localeOption.code,
|
||||
}
|
||||
const search = qs.stringify(newParams)
|
||||
const localeOptionLabel = getTranslation(localeOption.label, i18n)
|
||||
|
||||
return (
|
||||
<PopupList.Button key={localeOption.code} onClick={close} to={{ search }}>
|
||||
{localeOption.label}
|
||||
{localeOption.label !== localeOption.code && ` (${localeOption.code})`}
|
||||
{localeOptionLabel}
|
||||
{localeOptionLabel !== localeOption.code && ` (${localeOption.code})`}
|
||||
</PopupList.Button>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -134,7 +134,13 @@ export default joi.object({
|
||||
joi.array().items(
|
||||
joi.object().keys({
|
||||
code: joi.string(),
|
||||
label: joi.string(),
|
||||
label: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
joi.string(),
|
||||
joi.valid(false),
|
||||
),
|
||||
rtl: joi.boolean(),
|
||||
toString: joi.func(),
|
||||
}),
|
||||
|
||||
@@ -288,7 +288,7 @@ export type Locale = {
|
||||
* label of supported locale
|
||||
* @example "English"
|
||||
*/
|
||||
label: string
|
||||
label: string | Record<string, string>
|
||||
/**
|
||||
* if true, defaults textAligmnent on text fields to RTL
|
||||
*/
|
||||
|
||||
@@ -85,7 +85,22 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
localization: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'es'],
|
||||
locales: [
|
||||
{
|
||||
label: {
|
||||
es: 'Español',
|
||||
en: 'Spanish',
|
||||
},
|
||||
code: 'es',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
es: 'Inglés',
|
||||
en: 'English',
|
||||
},
|
||||
code: 'en',
|
||||
},
|
||||
],
|
||||
},
|
||||
collections: [
|
||||
Posts,
|
||||
|
||||
@@ -488,6 +488,43 @@ describe('admin', () => {
|
||||
'Home',
|
||||
)
|
||||
})
|
||||
|
||||
test('should allow custom translation of locale labels', async () => {
|
||||
const selectOptionClass = '.localizer .popup-button-list__button'
|
||||
const localizorButton = page.locator('.localizer .popup-button')
|
||||
const secondLocale = page.locator(selectOptionClass).nth(1)
|
||||
|
||||
async function checkLocalLabels(firstLabel: string, secondLabel: string) {
|
||||
await localizorButton.click()
|
||||
await expect(page.locator(selectOptionClass).first()).toContainText(firstLabel)
|
||||
await expect(page.locator(selectOptionClass).nth(1)).toContainText(secondLabel)
|
||||
}
|
||||
|
||||
await checkLocalLabels('English (en)', 'Spanish (es)')
|
||||
|
||||
// Change locale to Spanish
|
||||
await localizorButton.click()
|
||||
await expect(secondLocale).toContainText('Spanish (es)')
|
||||
await secondLocale.click()
|
||||
|
||||
// Go to account page
|
||||
await page.goto(url.account)
|
||||
|
||||
const languageField = page.locator('.payload-settings__language .react-select')
|
||||
const options = page.locator('.rs__option')
|
||||
|
||||
// Change language to Spanish
|
||||
await languageField.click()
|
||||
await options.locator('text=Español').click()
|
||||
|
||||
await checkLocalLabels('Inglés (en)', 'Español (es)')
|
||||
|
||||
// Change locale and language back to English
|
||||
await languageField.click()
|
||||
await options.locator('text=English').click()
|
||||
await localizorButton.click()
|
||||
await expect(secondLocale).toContainText('Spanish (es)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('list view', () => {
|
||||
|
||||
Reference in New Issue
Block a user