feat: adds memoization to translation functions (#5036)
This commit is contained in:
@@ -24,7 +24,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export const generateMetadata = async ({
|
||||
}: {
|
||||
config: Promise<SanitizedConfig>
|
||||
}): Promise<Metadata> => {
|
||||
const t = getNextT({
|
||||
const t = await getNextT({
|
||||
config: await config,
|
||||
})
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import { getAuthenticatedUser } from 'payload/auth'
|
||||
import { getPayload } from 'payload'
|
||||
import { URL } from 'url'
|
||||
import { parseCookies } from 'payload/auth'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { getRequestLanguage } from './getRequestLanguage'
|
||||
import { getRequestLocales } from './getRequestLocales'
|
||||
import { getNextI18n } from './getNextI18n'
|
||||
import { getDataAndFile } from './getDataAndFile'
|
||||
|
||||
type Args = {
|
||||
@@ -61,10 +61,10 @@ export const createPayloadRequest = async ({
|
||||
cookies,
|
||||
})
|
||||
|
||||
const i18n = getNextI18n({
|
||||
config,
|
||||
const i18n = await initI18n({
|
||||
config: config.i18n,
|
||||
language,
|
||||
translationContext: 'api',
|
||||
translationsContext: 'api',
|
||||
})
|
||||
|
||||
const customRequest: CustomPayloadRequest = {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { SanitizedConfig } from 'payload/types'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations as clientTranslations } from '@payloadcms/translations/client'
|
||||
import { translations as apiTranslations } from '@payloadcms/translations/api'
|
||||
|
||||
export const getNextI18n = ({
|
||||
config,
|
||||
language,
|
||||
translationContext = 'client',
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
language: string
|
||||
translationContext?: 'api' | 'client'
|
||||
}): ReturnType<typeof initI18n> => {
|
||||
return initI18n({
|
||||
config: config.i18n,
|
||||
language,
|
||||
translations: translationContext === 'api' ? apiTranslations : clientTranslations,
|
||||
})
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
import { translations } from '@payloadcms/translations/client'
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import type { SanitizedConfig } from 'payload/types'
|
||||
|
||||
import { initTFunction } from '@payloadcms/translations'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { cookies, headers } from 'next/headers'
|
||||
import { getRequestLanguage } from './getRequestLanguage'
|
||||
|
||||
export const getNextT = ({
|
||||
export const getNextT = async ({
|
||||
config,
|
||||
language,
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
language?: string
|
||||
}): TFunction => {
|
||||
const lang = language || getRequestLanguage({ cookies: cookies(), headers: headers() })
|
||||
|
||||
return initTFunction({
|
||||
language: lang,
|
||||
}): Promise<TFunction> => {
|
||||
const i18n = await initI18n({
|
||||
translationsContext: 'client',
|
||||
language: language || getRequestLanguage({ cookies: cookies(), headers: headers() }),
|
||||
config: config.i18n,
|
||||
translations,
|
||||
})
|
||||
|
||||
return i18n.t
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ import type {
|
||||
} from 'payload/types'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { parseCookies } from 'payload/auth'
|
||||
import { getNextI18n } from './getNextI18n'
|
||||
import { getRequestLanguage } from './getRequestLanguage'
|
||||
import { findLocaleFromCode } from '../../../ui/src/utilities/findLocaleFromCode'
|
||||
import { I18n } from '@payloadcms/translations/types'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
|
||||
export const initPage = async ({
|
||||
configPromise,
|
||||
@@ -31,7 +32,7 @@ export const initPage = async ({
|
||||
permissions: Awaited<ReturnType<typeof auth>>['permissions']
|
||||
user: Awaited<ReturnType<typeof auth>>['user']
|
||||
config: SanitizedConfig
|
||||
i18n: ReturnType<typeof getNextI18n>
|
||||
i18n: I18n
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
locale: ReturnType<typeof findLocaleFromCode>
|
||||
@@ -59,7 +60,11 @@ export const initPage = async ({
|
||||
config: configPromise,
|
||||
})
|
||||
|
||||
const i18n = getNextI18n({ config, language })
|
||||
const i18n = await initI18n({
|
||||
config: config.i18n,
|
||||
language,
|
||||
translationsContext: 'client',
|
||||
})
|
||||
let collectionConfig: SanitizedCollectionConfig
|
||||
let globalConfig: SanitizedGlobalConfig
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
|
||||
data,
|
||||
disableEmail,
|
||||
expiration,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
data,
|
||||
depth,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
|
||||
collection,
|
||||
data,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
|
||||
collection,
|
||||
data,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
return verifyEmailOperation({
|
||||
collection,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
token,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
|
||||
)
|
||||
}
|
||||
|
||||
const req = createLocalReq(options, payload)
|
||||
const req = await createLocalReq(options, payload)
|
||||
req.file = file ?? (await getFileByPath(filePath))
|
||||
|
||||
return createOperation<TSlug>({
|
||||
|
||||
@@ -76,7 +76,7 @@ async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
collection,
|
||||
depth,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
where,
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export default async function findLocal<T extends keyof GeneratedTypes['collecti
|
||||
overrideAccess,
|
||||
page,
|
||||
pagination,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
sort,
|
||||
where,
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function findByIDLocal<T extends keyof GeneratedTypes['coll
|
||||
disableErrors,
|
||||
draft,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
||||
depth,
|
||||
disableErrors,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
limit,
|
||||
overrideAccess,
|
||||
page,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
sort,
|
||||
where,
|
||||
|
||||
@@ -45,7 +45,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
depth,
|
||||
overrideAccess,
|
||||
payload,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
)
|
||||
}
|
||||
|
||||
const req = createLocalReq(options, payload)
|
||||
const req = await createLocalReq(options, payload)
|
||||
req.file = file ?? (await getFileByPath(filePath))
|
||||
|
||||
const args = {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default async function findOneLocal<T extends keyof GeneratedTypes['globa
|
||||
draft,
|
||||
globalConfig,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
slug: globalSlug as string,
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
||||
disableErrors,
|
||||
globalConfig,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
|
||||
limit,
|
||||
overrideAccess,
|
||||
page,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
sort,
|
||||
where,
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
|
||||
depth,
|
||||
globalConfig,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default async function updateLocal<TSlug extends keyof GeneratedTypes['gl
|
||||
draft,
|
||||
globalConfig,
|
||||
overrideAccess,
|
||||
req: createLocalReq(options, payload),
|
||||
req: await createLocalReq(options, payload),
|
||||
showHiddenFields,
|
||||
slug: globalSlug as string,
|
||||
})
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
|
||||
import type { SanitizedConfig } from '../exports/types'
|
||||
|
||||
export const getLocalI18n = ({
|
||||
export const getLocalI18n = async ({
|
||||
config,
|
||||
language = 'en',
|
||||
}: {
|
||||
config: SanitizedConfig
|
||||
language?: string
|
||||
}): ReturnType<typeof initI18n> =>
|
||||
}) =>
|
||||
initI18n({
|
||||
config: config.i18n,
|
||||
language,
|
||||
translations,
|
||||
translationsContext: 'api',
|
||||
})
|
||||
|
||||
@@ -29,12 +29,12 @@ type CreateLocalReq = (
|
||||
user?: Document
|
||||
},
|
||||
payload: Payload,
|
||||
) => PayloadRequest
|
||||
export const createLocalReq: CreateLocalReq = (
|
||||
) => Promise<PayloadRequest>
|
||||
export const createLocalReq: CreateLocalReq = async (
|
||||
{ context, fallbackLocale, locale, req = {} as PayloadRequest, user },
|
||||
payload,
|
||||
) => {
|
||||
const i18n = req?.i18n || getLocalI18n({ config: payload.config })
|
||||
const i18n = req?.i18n || (await getLocalI18n({ config: payload.config }))
|
||||
|
||||
if (payload.config?.localization) {
|
||||
const defaultLocale = payload.config.localization.defaultLocale
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm build"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/exports/index.ts",
|
||||
"require": "./src/exports/index.ts",
|
||||
"types": "./src/exports/index.ts"
|
||||
},
|
||||
"./api": {
|
||||
"import": "./src/all/index.ts",
|
||||
"require": "./src/all/index.ts",
|
||||
@@ -19,11 +24,6 @@
|
||||
"import": "./src/all/index.ts",
|
||||
"require": "./src/all/index.ts",
|
||||
"types": "./src/all/index.ts"
|
||||
},
|
||||
".": {
|
||||
"import": "./src/exports/index.ts",
|
||||
"require": "./src/exports/index.ts",
|
||||
"types": "./src/exports/index.ts"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -33,13 +33,17 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/exports/index.js",
|
||||
"require": "./dist/exports/index.js"
|
||||
},
|
||||
"./api": {
|
||||
"import": "./dist/api/index.ts",
|
||||
"require": "./dist/api/index.ts"
|
||||
"import": "./dist/api/index.js",
|
||||
"require": "./dist/api/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"import": "./dist/client/index.ts",
|
||||
"require": "./dist/client/index.ts"
|
||||
"import": "./dist/client/index.js",
|
||||
"require": "./dist/client/index.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/exports/index.js",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { initI18n, t, initTFunction, matchLanguage } from '../utilities/init'
|
||||
export { initI18n, t, matchLanguage } from '../utilities/init'
|
||||
export { getTranslation } from '../utilities/getTranslation'
|
||||
export type * from '../types'
|
||||
|
||||
@@ -42,3 +42,9 @@ export type InitTFunction = (args: {
|
||||
language?: string
|
||||
translations?: Translations
|
||||
}) => TFunction
|
||||
|
||||
export type InitI18n = (args: {
|
||||
config: I18nOptions
|
||||
language?: string
|
||||
translationsContext: 'client' | 'api'
|
||||
}) => Promise<I18n>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { JSX } from 'react'
|
||||
import type { initI18n } from './init'
|
||||
import { I18n } from '../types'
|
||||
|
||||
export const getTranslation = (
|
||||
label: JSX.Element | Record<string, string> | string,
|
||||
i18n: Pick<ReturnType<typeof initI18n>, 'fallbackLanguage' | 'language'>,
|
||||
i18n: Pick<I18n, 'fallbackLanguage' | 'language'>,
|
||||
): string => {
|
||||
if (typeof label === 'object') {
|
||||
if (label[i18n.language]) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { I18n, Translations, InitTFunction } from '../types'
|
||||
import { Translations, InitTFunction, InitI18n, I18n } from '../types'
|
||||
import { deepMerge } from './deepMerge'
|
||||
|
||||
/**
|
||||
@@ -191,7 +191,7 @@ export function matchLanguage(header: string): string | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const initTFunction: InitTFunction = (args) => (key, vars) => {
|
||||
const initTFunction: InitTFunction = (args) => (key, vars) => {
|
||||
const { config, language, translations } = args
|
||||
|
||||
const mergedLanguages = deepMerge(config?.translations ?? {}, translations)
|
||||
@@ -204,18 +204,58 @@ export const initTFunction: InitTFunction = (args) => (key, vars) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const initI18n = ({
|
||||
config,
|
||||
language = 'en',
|
||||
translations,
|
||||
}: Parameters<InitTFunction>[0]): I18n => {
|
||||
return {
|
||||
fallbackLanguage: config.fallbackLanguage,
|
||||
language: language || config.fallbackLanguage,
|
||||
t: initTFunction({
|
||||
config,
|
||||
language: language || config.fallbackLanguage,
|
||||
translations,
|
||||
}),
|
||||
function memoize<T>(fn: Function, keys: string[]): T {
|
||||
const cacheMap = new Map()
|
||||
|
||||
return <T>async function (args) {
|
||||
const cacheKey = keys.reduce((acc, key) => acc + args[key], '')
|
||||
|
||||
if (!cacheMap.has(cacheKey)) {
|
||||
const result = await fn(args)
|
||||
cacheMap.set(cacheKey, result)
|
||||
}
|
||||
|
||||
return cacheMap.get(cacheKey)!
|
||||
}
|
||||
}
|
||||
|
||||
type GetTranslationsByKey = ({ context }: { context: 'client' | 'api' }) => Promise<Translations>
|
||||
const getTranslationsByKey: GetTranslationsByKey = memoize(
|
||||
<GetTranslationsByKey>(async ({ context }): Promise<Translations> => {
|
||||
const cachedTranslations = new Map<string, Translations>()
|
||||
if (cachedTranslations.has(context)) {
|
||||
return cachedTranslations.get(context)
|
||||
}
|
||||
|
||||
let translations = {}
|
||||
if (context === 'api') {
|
||||
translations = await import('@payloadcms/translations/api')
|
||||
cachedTranslations.set(context, translations)
|
||||
} else if (context === 'client') {
|
||||
translations = await import('@payloadcms/translations/client')
|
||||
cachedTranslations.set(context, translations)
|
||||
}
|
||||
|
||||
return translations
|
||||
}),
|
||||
['context'] satisfies Array<keyof Parameters<GetTranslationsByKey>[0]>,
|
||||
)
|
||||
|
||||
export const initI18n: InitI18n = memoize(
|
||||
<InitI18n>(async ({ config, language = 'en', translationsContext }) => {
|
||||
const translations = await getTranslationsByKey({ context: translationsContext })
|
||||
|
||||
const i18n = {
|
||||
fallbackLanguage: config.fallbackLanguage,
|
||||
language: language || config.fallbackLanguage,
|
||||
t: initTFunction({
|
||||
config,
|
||||
language: language || config.fallbackLanguage,
|
||||
translations,
|
||||
}),
|
||||
}
|
||||
|
||||
return i18n
|
||||
}),
|
||||
['language', 'translationsContext'] satisfies Array<keyof Parameters<InitI18n>[0]>,
|
||||
)
|
||||
|
||||
@@ -8,8 +8,7 @@ import { reduceFieldsToValues } from '../..'
|
||||
import { DocumentPreferences } from 'payload/types'
|
||||
import { Locale } from 'payload/config'
|
||||
import { User } from 'payload/auth'
|
||||
import { initTFunction } from '@payloadcms/translations'
|
||||
import { translations } from '@payloadcms/translations/api'
|
||||
import { initI18n } from '@payloadcms/translations'
|
||||
|
||||
export const getFormStateFromServer = async (
|
||||
args: {
|
||||
@@ -37,8 +36,11 @@ export const getFormStateFromServer = async (
|
||||
|
||||
const data = reduceFieldsToValues(formState, true)
|
||||
|
||||
// TODO: memoize the creation of this function based on language
|
||||
const t = initTFunction({ config: payload.config.i18n, language, translations })
|
||||
const { t } = await initI18n({
|
||||
translationsContext: 'client',
|
||||
language: language,
|
||||
config: payload.config.i18n,
|
||||
})
|
||||
|
||||
const result = await buildStateFromSchema({
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user