Skip to content

Commit 623a1b8

Browse files
feat: accept multiple locales in fallbackLocale (#13822)
### What? This change allows `fallbackLocale` to accept an **array of locales** in find queries and locale configuration. ```ts /** Local API **/ await payload.findByID({ id, collection, locale: 'en', fallbackLocale: ['fr', 'es'], }) /** REST API **/ await fetch(`${baseURL}/api/${collectionSlug}?locale=en&fallbackLocale[]=fr&fallbackLocale[]=es`) /** GraphQL **/ await restClient.GRAPHQL_POST({ body, query: { locale: 'en', fallbackLocale: ['fr', 'es']}, }) /** Locale Configs **/ locales: [ { code: 'en', label: 'English', fallbackLocale: ['fr', 'es'], }, ] ``` ### Why? This update is part of the planned [localization enhancements](#10705). Currently, only one fallback locale can be specified. If that locale doesn’t contain data, the query returns nothing. To work around this, users have to inspect the response themselves and then make additional queries for other locales. With this change, Payload handles that work automatically. Users only need to provide a list of fallback locales in their preferred order, and Payload will check each one until it finds a value. ### How? Updates query handling across the local API, REST API, and GraphQL so that `fallbackLocale` can accept either a **string** or an **array** of locales. When an array is passed, Payload will iterate through them in order and return the first found localized value. This behavior applies to both collections and globals. #### Feature request: #13443 --------- Co-authored-by: Jarrod Flesch <[email protected]>
1 parent 13a1d90 commit 623a1b8

File tree

21 files changed

+333
-35
lines changed

21 files changed

+333
-35
lines changed

docs/configuration/localization.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ The locale codes do not need to be in any specific format. It's up to you to def
9393

9494
#### Locale Object
9595

96-
| Option | Description |
97-
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
98-
| **`code`** \* | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
99-
| **`label`** | A string to use for the selector when choosing a language, or an object keyed on the i18n keys for different languages in use. |
100-
| **`rtl`** | A boolean that when true will make the admin UI display in Right-To-Left. |
101-
| **`fallbackLocale`** | The code for this language to fallback to when properties of a document are not present. |
96+
| Option | Description |
97+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
98+
| **`code`** \* | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
99+
| **`label`** | A string to use for the selector when choosing a language, or an object keyed on the i18n keys for different languages in use. |
100+
| **`rtl`** | A boolean that when true will make the admin UI display in Right-To-Left. |
101+
| **`fallbackLocale`** | The code for this language to fallback to when properties of a document are not present. This can be a single locale or array of locales. |
102102

103103
_\* An asterisk denotes that a property is required._
104104

@@ -199,7 +199,7 @@ The `locale` arg will only accept valid locales, but locales will be formatted a
199199
values (dashes or special characters will be converted to underscores, spaces will be removed, etc.). If you are curious
200200
to see how locales are auto-formatted, you can use the [GraphQL playground](/docs/graphql/overview#graphql-playground).
201201

202-
The `fallbackLocale` arg will accept valid locales as well as `none` to disable falling back.
202+
The `fallbackLocale` arg will accept valid locales, an array of locales, as well as `none` to disable falling back.
203203

204204
**Example:**
205205

@@ -224,7 +224,7 @@ query {
224224

225225
You can specify `locale` as well as `fallbackLocale` within the Local API as well as properties on the `options`
226226
argument. The `locale` property will accept any valid locale, and the `fallbackLocale` property will accept any valid
227-
locale as well as `'null'`, `'false'`, `false`, and `'none'`.
227+
locale, array of locales, as well as `'null'`, `'false'`, `false`, and `'none'`.
228228

229229
**Example:**
230230

docs/local-api/overview.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ You can specify more options within the Local API vs. REST or GraphQL due to the
7171
| `locale` | Specify [locale](/docs/configuration/localization) for any returned documents. |
7272
| `select` | Specify [select](../queries/select) to control which fields to include to the result. |
7373
| `populate` | Specify [populate](../queries/select#populate) to control which fields to include to the result from populated documents. |
74-
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. |
74+
| `fallbackLocale` | Specify a [fallback locale](/docs/configuration/localization) to use for any returned documents. This can be a single locale or array of locales. |
7575
| `overrideAccess` | Skip access control. By default, this property is set to true within all Local API operations. |
7676
| `overrideLock` | By default, document locks are ignored (`true`). Set to `false` to enforce locks and prevent operations when a document is locked by another user. [More details](../admin/locked-documents). |
7777
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |

packages/payload/src/admin/RichText.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export type AfterReadRichTextHookArgs<
3131

3232
draft?: boolean
3333

34-
fallbackLocale?: string
34+
fallbackLocale?: string | string[]
3535
fieldPromises?: Promise<void>[]
3636

3737
/** Boolean to denote if this hook is running against finding one, or finding many within the afterRead hook. */

packages/payload/src/collections/dataloader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,8 @@ type CreateCacheKeyArgs = {
225225
depth: number
226226
docID: number | string
227227
draft: boolean
228-
fallbackLocale: string
229-
locale: string
228+
fallbackLocale: string | string[]
229+
locale: string | string[]
230230
overrideAccess: boolean
231231
populate?: PopulateType
232232
select?: SelectType

packages/payload/src/collections/operations/local/find.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export type Options<TSlug extends CollectionSlug, TSelect extends SelectType> =
5454
/**
5555
* Specify a [fallback locale](https://payloadcms.com/docs/configuration/localization) to use for any returned documents.
5656
*/
57-
fallbackLocale?: false | TypedLocale
57+
fallbackLocale?: false | TypedLocale | TypedLocale[]
5858
/**
5959
* Include info about the lock status to the result into all documents with fields: `_isLocked` and `_userEditing`
6060
*/

packages/payload/src/collections/operations/local/findByID.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export type Options<
6262
/**
6363
* Specify a [fallback locale](https://payloadcms.com/docs/configuration/localization) to use for any returned documents.
6464
*/
65-
fallbackLocale?: false | TypedLocale
65+
fallbackLocale?: false | TypedLocale | TypedLocale[]
6666
/**
6767
* The ID of the document to find.
6868
*/

packages/payload/src/collections/operations/utilities/update.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export type SharedUpdateDocumentArgs<TSlug extends CollectionSlug> = {
4343
depth: number
4444
docWithLocales: any
4545
draftArg: boolean
46-
fallbackLocale: string
46+
fallbackLocale: string | string[]
4747
filesToUpload: FileToSave[]
4848
id: number | string
4949
locale: string

packages/payload/src/config/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ export type Locale = {
470470
/**
471471
* Code of another locale to use when reading documents with fallback, if not specified defaultLocale is used
472472
*/
473-
fallbackLocale?: string
473+
fallbackLocale?: string | string[]
474474
/**
475475
* label of supported locale
476476
* @example "English"

packages/payload/src/fields/hooks/afterRead/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export type AfterReadArgs<T extends JsonObject> = {
1313
depth: number
1414
doc: T
1515
draft: boolean
16-
fallbackLocale: null | string
16+
fallbackLocale: null | string | string[]
1717
findMany?: boolean
1818
/**
1919
* Controls whether locales should be flattened into the requested locale.

packages/payload/src/fields/hooks/afterRead/promise.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type Args = {
3333
depth: number
3434
doc: JsonObject
3535
draft: boolean
36-
fallbackLocale: null | string
36+
fallbackLocale: null | string | string[]
3737
field: Field | TabAsField
3838
fieldIndex: number
3939
/**
@@ -158,9 +158,21 @@ export const promise = async ({
158158
let hoistedValue = value
159159

160160
if (fallbackLocale && fallbackLocale !== locale) {
161-
const fallbackValue = siblingDoc[field.name!][fallbackLocale]
161+
let fallbackValue
162162
const isNullOrUndefined = typeof value === 'undefined' || value === null
163163

164+
if (Array.isArray(fallbackLocale)) {
165+
for (const locale of fallbackLocale) {
166+
const val = siblingDoc[field.name!]?.[locale]
167+
if (val !== undefined && val !== null && val !== '') {
168+
fallbackValue = val
169+
break
170+
}
171+
}
172+
} else {
173+
fallbackValue = siblingDoc[field.name!][fallbackLocale]
174+
}
175+
164176
if (fallbackValue) {
165177
switch (field.type) {
166178
case 'text':

0 commit comments

Comments
 (0)