fix(ui): switching languages does not update cached client config (#11725)
### What?
Fixed client config caching to properly update when switching languages
in the admin UI.
### Why?
Currently, switching languages doesn't fully update the UI because
client config stays cached with previous language translations.
### How?
Created a language-aware caching system that stores separate configs for
each language and only uses cached config when it matches the active
language.
Before:
```typescript
let cachedClientConfig: ClientConfig | null = global._payload_clientConfig
if (!cachedClientConfig) {
cachedClientConfig = global._payload_clientConfig = null
}
export const getClientConfig = cache(
(args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
if (cachedClientConfig && !global._payload_doNotCacheClientConfig) {
return cachedClientConfig
}
// ... create new config ...
}
);
```
After:
```typescript
let cachedClientConfigs: Record<string, ClientConfig> = global._payload_localizedClientConfigs
if (!cachedClientConfigs) {
cachedClientConfigs = global._payload_localizedClientConfigs = {}
}
export const getClientConfig = cache(
(args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
const { config, i18n, importMap } = args
const currentLocale = i18n.language
if (!global._payload_doNotCacheClientConfig && cachedClientConfigs[currentLocale]) {
return cachedClientConfigs[currentLocale]
}
// ... create new config with correct translations ...
}
);
```
Also added handling for cache clearing during HMR to ensure
compatibility with the existing system.
Fixes #11406
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
This commit is contained in:
@@ -65,8 +65,11 @@ import type {
|
|||||||
} from './types/index.js'
|
} from './types/index.js'
|
||||||
import type { TraverseFieldsCallback } from './utilities/traverseFields.js'
|
import type { TraverseFieldsCallback } from './utilities/traverseFields.js'
|
||||||
export type * from './admin/types.js'
|
export type * from './admin/types.js'
|
||||||
|
import type { SupportedLanguages } from '@payloadcms/translations'
|
||||||
|
|
||||||
import { Cron } from 'croner'
|
import { Cron } from 'croner'
|
||||||
|
|
||||||
|
import type { ClientConfig } from './config/client.js'
|
||||||
import type { TypeWithVersion } from './versions/types.js'
|
import type { TypeWithVersion } from './versions/types.js'
|
||||||
|
|
||||||
import { decrypt, encrypt } from './auth/crypto.js'
|
import { decrypt, encrypt } from './auth/crypto.js'
|
||||||
@@ -865,10 +868,12 @@ export const reload = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await payload.db.init()
|
await payload.db.init()
|
||||||
|
|
||||||
if (payload.db.connect) {
|
if (payload.db.connect) {
|
||||||
await payload.db.connect({ hotReload: true })
|
await payload.db.connect({ hotReload: true })
|
||||||
}
|
}
|
||||||
global._payload_clientConfig = null
|
|
||||||
|
global._payload_clientConfigs = {} as Record<keyof SupportedLanguages, ClientConfig>
|
||||||
global._payload_schemaMap = null
|
global._payload_schemaMap = null
|
||||||
global._payload_clientSchemaMap = null
|
global._payload_clientSchemaMap = null
|
||||||
global._payload_doNotCacheClientConfig = true // This will help refreshing the client config cache more reliably. If you remove this, please test HMR + client config refreshing (do new fields appear in the document?)
|
global._payload_doNotCacheClientConfig = true // This will help refreshing the client config cache more reliably. If you remove this, please test HMR + client config refreshing (do new fields appear in the document?)
|
||||||
|
|||||||
@@ -1,31 +1,38 @@
|
|||||||
import type { I18nClient } from '@payloadcms/translations'
|
import type { I18nClient, SupportedLanguages } from '@payloadcms/translations'
|
||||||
import type { ClientConfig, ImportMap, SanitizedConfig } from 'payload'
|
import type { ClientConfig, ImportMap, SanitizedConfig } from 'payload'
|
||||||
|
|
||||||
import { createClientConfig } from 'payload'
|
import { createClientConfig } from 'payload'
|
||||||
import { cache } from 'react'
|
import { cache } from 'react'
|
||||||
|
|
||||||
let cachedClientConfig: ClientConfig | null = global._payload_clientConfig
|
let cachedClientConfigs = global._payload_clientConfigs as Record<
|
||||||
|
keyof SupportedLanguages,
|
||||||
|
ClientConfig
|
||||||
|
>
|
||||||
|
|
||||||
if (!cachedClientConfig) {
|
if (!cachedClientConfigs) {
|
||||||
cachedClientConfig = global._payload_clientConfig = null
|
cachedClientConfigs = global._payload_clientConfigs = {} as Record<
|
||||||
|
keyof SupportedLanguages,
|
||||||
|
ClientConfig
|
||||||
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getClientConfig = cache(
|
export const getClientConfig = cache(
|
||||||
(args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
|
(args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
|
||||||
if (cachedClientConfig && !global._payload_doNotCacheClientConfig) {
|
const { config, i18n, importMap } = args
|
||||||
return cachedClientConfig
|
const currentLanguage = i18n.language
|
||||||
|
|
||||||
|
if (cachedClientConfigs[currentLanguage] && !global._payload_doNotCacheClientConfig) {
|
||||||
|
return cachedClientConfigs[currentLanguage]
|
||||||
}
|
}
|
||||||
|
|
||||||
const { config, i18n, importMap } = args
|
const cachedClientConfig = createClientConfig({
|
||||||
|
|
||||||
cachedClientConfig = createClientConfig({
|
|
||||||
config,
|
config,
|
||||||
i18n,
|
i18n,
|
||||||
importMap,
|
importMap,
|
||||||
})
|
})
|
||||||
|
|
||||||
global._payload_clientConfig = cachedClientConfig
|
cachedClientConfigs[currentLanguage] = cachedClientConfig
|
||||||
|
global._payload_clientConfigs = cachedClientConfigs
|
||||||
global._payload_doNotCacheClientConfig = false
|
global._payload_doNotCacheClientConfig = false
|
||||||
|
|
||||||
return cachedClientConfig
|
return cachedClientConfig
|
||||||
|
|||||||
@@ -82,4 +82,27 @@ describe('i18n', () => {
|
|||||||
page.locator('.componentWithCustomI18n .componentWithCustomI18nCustomValidI18nT'),
|
page.locator('.componentWithCustomI18n .componentWithCustomI18nCustomValidI18nT'),
|
||||||
).toHaveText('My custom translation')
|
).toHaveText('My custom translation')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ensure translations update correctly when switching language', async () => {
|
||||||
|
await page.goto(serverURL + '/admin/account')
|
||||||
|
|
||||||
|
await page.locator('div.rs__control').click()
|
||||||
|
await page.locator('div.rs__option').filter({ hasText: 'English' }).click()
|
||||||
|
await expect(page.locator('div.payload-settings h3')).toHaveText('Payload Settings')
|
||||||
|
|
||||||
|
await page.goto(serverURL + '/admin/collections/collection1/create')
|
||||||
|
await expect(page.locator('label[for="field-fieldDefaultI18nValid"]')).toHaveText(
|
||||||
|
'Add {{label}}',
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.goto(serverURL + '/admin/account')
|
||||||
|
await page.locator('div.rs__control').click()
|
||||||
|
await page.locator('div.rs__option').filter({ hasText: 'Español' }).click()
|
||||||
|
await expect(page.locator('div.payload-settings h3')).toHaveText('Configuración de la carga')
|
||||||
|
|
||||||
|
await page.goto(serverURL + '/admin/collections/collection1/create')
|
||||||
|
await expect(page.locator('label[for="field-fieldDefaultI18nValid"]')).toHaveText(
|
||||||
|
'Añadir {{label}}',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user