Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: filterAvailableLocales #11031

Merged
merged 4 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions docs/configuration/localization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@ export default buildConfig({

The following options are available:

| Option | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **`locales`** | Array of all the languages that you would like to support. [More details](#locales) |
| **`defaultLocale`** | Required string that matches one of the locale codes from the array provided. By default, if no locale is specified, documents will be returned in this locale. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated unless a fallback is explicitly provided in the request. True by default. |
| Option | Description |
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`locales`** | Array of all the languages that you would like to support. [More details](#locales) |
| **`defaultLocale`** | Required string that matches one of the locale codes from the array provided. By default, if no locale is specified, documents will be returned in this locale. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated unless a fallback is explicitly provided in the request. True by default. |
| **`filterAvailableLocales`** | A function that is called with the array of `locales` and the `req`, it should return locales to show in admin UI selector. [See more](#filter-available-options). |

### Locales

Expand All @@ -100,6 +101,35 @@ The locale codes do not need to be in any specific format. It's up to you to def

_* An asterisk denotes that a property is required._

#### Filter Available Options
In some projects you may want to filter the available locales shown in the admin UI selector. You can do this by providing a `filterAvailableLocales` function in your Payload Config. This is called on the server side and is passed the array of locales. This means that you can determine what locales are visible in the localizer selection menu at the top of the admin panel. You could do this per user, or implement a function that scopes these to tenants and more. Here is an example using request headers in a multi-tenant application:

```ts
// ... rest of payload config
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
filterAvailableLocales: async ({ req, locales }) => {
if (getTenantFromCookie(req.headers, 'text')) {
const fullTenant = await req.payload.findByID({
id: getTenantFromCookie(req.headers, 'text') as string,
collection: 'tenants',
req,
})
if (fullTenant && fullTenant.supportedLocales?.length) {
return locales.filter((locale) => {
return fullTenant.supportedLocales?.includes(locale.code as 'en' | 'es')
})
}
}
return locales
},
}
```

Since the filtering happens at the root level of the application and its result is not calculated every time you navigate to a new page, you may want to call `router.refresh` in a custom component that watches when values that affect the result change. In the example above, you would want to do this when `supportedLocales` changes on the tenant document.


## Field Localization

Payload Localization works on a **field** level—not a document level. In addition to configuring the base Payload Config to support Localization, you need to specify each field that you would like to localize.
Expand Down
7 changes: 7 additions & 0 deletions test/localization/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
update: () => true,
}

export default buildConfigWithDefaults({

Check warning on line 55 in test/localization/config.ts

View workflow job for this annotation

GitHub Actions / lint

Exporting 'default' is restricted
admin: {
importMap: {
baseDir: path.resolve(dirname),
Expand Down Expand Up @@ -414,9 +414,16 @@
},
],
localization: {
filterAvailableLocales: ({ locales }) => {
return locales.filter((locale) => locale.code !== 'xx')
},
defaultLocale,
fallback: true,
locales: [
{
code: 'xx',
label: 'FILTERED',
},
{
code: defaultLocale,
label: 'English',
Expand Down
7 changes: 7 additions & 0 deletions test/localization/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
// delay: 'Fast 4G',
// })
})

Check failure on line 99 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

No async describe callback

Check failure on line 99 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Async arrow function has no 'await' expression
describe('localizer', async () => {
test('should show localizer controls', async () => {
await page.goto(url.create)
Expand All @@ -105,6 +105,13 @@
await expect(page.locator('.localizer .popup.popup--active')).toBeVisible()
})

test('should filter locale with filterAvailableLocales', async () => {
await page.goto(url.create)
await expect(page.locator('.localizer.app-header__localizer')).toBeVisible()
await page.locator('.localizer >> button').first().click()
await expect(page.locator('.localizer .popup.popup--active')).not.toContainText('FILTERED')
})

test('should disable control for active locale', async () => {
await page.goto(url.create)

Expand All @@ -117,10 +124,10 @@
)

await expect(activeOption).toBeVisible()
const tagName = await activeOption.evaluate((node) => node.tagName)

Check warning on line 127 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Non-retryable, flaky assertion used in Playwright test: "toBe". Those need to be wrapped in expect.poll() or expect().toPass()
await expect(tagName).not.toBe('A')
await expect(activeOption).not.toHaveAttribute('href')

Check warning on line 129 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Non-retryable, flaky assertion used in Playwright test: "toBe". Those need to be wrapped in expect.poll() or expect().toPass()
await expect(tagName).not.toBe('BUTTON')

Check warning on line 130 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Non-retryable, flaky assertion used in Playwright test: "toBe". Those need to be wrapped in expect.poll() or expect().toPass()
await expect(tagName).toBe('DIV')
})
})
Expand Down Expand Up @@ -313,7 +320,7 @@
await expect(page.locator('.row-1 .cell-title')).toContainText(spanishTitle)
})

test('should not render default locale in locale selector when prefs are not default', async () => {

Check warning on line 323 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
await upsertPrefs<Config, GeneratedTypes<any>>({
payload,
user: client.user,
Expand Down Expand Up @@ -471,7 +478,7 @@
await setToLocale(page, 'Spanish')
await runCopy(page)
await expect(page.locator('#field-title')).toHaveValue(title)

Check failure on line 481 in test/localization/e2e.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Use a regular expression literal instead of the 'RegExp' constructor
const regexPattern = new RegExp(`locale=es`)
await expect(page).toHaveURL(regexPattern)

Expand Down
2 changes: 1 addition & 1 deletion test/localization/payload-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface Config {
'global-array': GlobalArraySelect<false> | GlobalArraySelect<true>;
'global-text': GlobalTextSelect<false> | GlobalTextSelect<true>;
};
locale: 'en' | 'es' | 'pt' | 'ar' | 'hu';
locale: 'xx' | 'en' | 'es' | 'pt' | 'ar' | 'hu';
user: User & {
collection: 'users';
};
Expand Down
Loading