feat: add internationalization (i18n) to locales (#4005)

This commit is contained in:
Jørgen Kalsnes Hagen
2023-11-08 18:56:15 +01:00
committed by GitHub
parent 57da3c99a7
commit 6a0a859563
7 changed files with 114 additions and 13 deletions

View File

@@ -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`**

View File

@@ -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>
&nbsp;&nbsp;
<span className={`${baseClass}__current-label`}>{`${locale.label}`}</span>
<span className={`${baseClass}__current-label`}>{`${getTranslation(
locale.label,
i18n,
)}`}</span>
&nbsp;
<Chevron className={`${baseClass}__chevron`} />
</div>

View File

@@ -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>
)
})}

View File

@@ -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(),
}),

View File

@@ -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
*/

View File

@@ -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,

View File

@@ -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', () => {