From b750cdbabbcd37a46e3e23fb66bac7ba2fddcd86 Mon Sep 17 00:00:00 2001 From: "maximilian.ebenkofler" Date: Sat, 30 May 2026 12:11:26 +0200 Subject: [PATCH 1/2] feat(plugin-nested-docs): support for translations Introduces translation keys and default translations for the nested docs plugin fields (breadcrumbs, parent). This enables the plugin to display its UI labels in multiple languages within the Payload admin, improving user experience for internationalized installations. The plugin now registers its own translation namespace, allowing its field labels to be localized and overridden through the main Payload i18n configuration. --- packages/plugin-nested-docs/package.json | 1 + .../src/fields/breadcrumbs.ts | 13 ++++++- .../plugin-nested-docs/src/fields/parent.ts | 2 ++ packages/plugin-nested-docs/src/index.ts | 8 +++++ .../src/translations/index.ts | 15 ++++++++ .../src/translations/languages/de.ts | 13 +++++++ .../src/translations/languages/en.ts | 13 +++++++ .../src/translations/languages/it.ts | 13 +++++++ .../src/translations/translation-schema.json | 36 +++++++++++++++++++ .../src/translations/types.ts | 3 ++ test/plugin-nested-docs/collections/Pages.ts | 2 +- test/plugin-nested-docs/payload-types.ts | 14 ++++++++ 12 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-nested-docs/src/translations/index.ts create mode 100644 packages/plugin-nested-docs/src/translations/languages/de.ts create mode 100644 packages/plugin-nested-docs/src/translations/languages/en.ts create mode 100644 packages/plugin-nested-docs/src/translations/languages/it.ts create mode 100644 packages/plugin-nested-docs/src/translations/translation-schema.json create mode 100644 packages/plugin-nested-docs/src/translations/types.ts diff --git a/packages/plugin-nested-docs/package.json b/packages/plugin-nested-docs/package.json index d006791427d..188168668c6 100644 --- a/packages/plugin-nested-docs/package.json +++ b/packages/plugin-nested-docs/package.json @@ -47,6 +47,7 @@ "lint:fix": "eslint . --fix" }, "devDependencies": { + "@payloadcms/translations": "workspace:*", "@payloadcms/eslint-config": "workspace:*", "payload": "workspace:*" }, diff --git a/packages/plugin-nested-docs/src/fields/breadcrumbs.ts b/packages/plugin-nested-docs/src/fields/breadcrumbs.ts index 28400242fe2..8a89fb06f5d 100644 --- a/packages/plugin-nested-docs/src/fields/breadcrumbs.ts +++ b/packages/plugin-nested-docs/src/fields/breadcrumbs.ts @@ -6,6 +6,12 @@ export const createBreadcrumbsField = ( ): Field => ({ name: 'breadcrumbs', type: 'array', + labels: { + // @ts-expect-error - translations are not typed in plugins yet + plural: ({ t }) => t('plugin-nested-docs:breadcrumbsPlural'), + // @ts-expect-error - translations are not typed in plugins yet + singular: ({ t }) => t('plugin-nested-docs:breadcrumbsSingular'), + }, localized: true, ...(overrides || {}), admin: { @@ -19,6 +25,8 @@ export const createBreadcrumbsField = ( admin: { disabled: true, }, + // @ts-expect-error - translations are not typed in plugins yet + label: ({ t }) => t('plugin-nested-docs:doc'), maxDepth: 0, relationTo, }, @@ -31,7 +39,8 @@ export const createBreadcrumbsField = ( admin: { width: '50%', }, - label: 'URL', + // @ts-expect-error - translations are not typed in plugins yet + label: ({ t }) => t('plugin-nested-docs:url'), }, { name: 'label', @@ -39,6 +48,8 @@ export const createBreadcrumbsField = ( admin: { width: '50%', }, + // @ts-expect-error - translations are not typed in plugins yet + label: ({ t }) => t('plugin-nested-docs:label'), }, ], }, diff --git a/packages/plugin-nested-docs/src/fields/parent.ts b/packages/plugin-nested-docs/src/fields/parent.ts index 422f6b0ce16..caa9cae0983 100644 --- a/packages/plugin-nested-docs/src/fields/parent.ts +++ b/packages/plugin-nested-docs/src/fields/parent.ts @@ -13,6 +13,8 @@ export const createParentField = ( position: 'sidebar', ...(overrides?.admin || {}), }, + // @ts-expect-error - translations are not typed in plugins yet + label: ({ t }) => t('plugin-nested-docs:parent'), // filterOptions are assigned dynamically based on the pluginConfig // filterOptions: parentFilterOptions(), type: 'relationship', diff --git a/packages/plugin-nested-docs/src/index.ts b/packages/plugin-nested-docs/src/index.ts index 61d78ed46e8..f0c2d401539 100644 --- a/packages/plugin-nested-docs/src/index.ts +++ b/packages/plugin-nested-docs/src/index.ts @@ -2,6 +2,8 @@ import type { SingleRelationshipField } from 'payload' import { definePlugin } from 'payload' +import { deepMergeSimple } from 'payload/shared' + import type { NestedDocsPluginConfig } from './types.js' import { createBreadcrumbsField } from './fields/breadcrumbs.js' @@ -10,7 +12,9 @@ import { parentFilterOptions } from './fields/parentFilterOptions.js' import { populateBreadcrumbsBeforeChange } from './hooks/populateBreadcrumbsBeforeChange.js' import { resaveChildren } from './hooks/resaveChildren.js' import { resaveSelfAfterCreate } from './hooks/resaveSelfAfterCreate.js' +import { translations } from './translations/index.js' import { getParents } from './utilities/getParents.js' +export { translations as nestedDocsTranslations } from './translations/index.js' export { createBreadcrumbsField, createParentField, getParents } @@ -69,5 +73,9 @@ export const nestedDocsPlugin = definePlugin({ return collection }), + i18n: { + ...config.i18n, + translations: deepMergeSimple(translations, config.i18n?.translations ?? {}), + }, }), }) diff --git a/packages/plugin-nested-docs/src/translations/index.ts b/packages/plugin-nested-docs/src/translations/index.ts new file mode 100644 index 00000000000..92493632c38 --- /dev/null +++ b/packages/plugin-nested-docs/src/translations/index.ts @@ -0,0 +1,15 @@ +import type { GenericTranslationsObject, NestedKeysStripped } from '@payloadcms/translations' + +import { de } from './languages/de.js' +import { en } from './languages/en.js' +import { it } from './languages/it.js' + +export const translations = { + de, + en, + it, +} + +export type PluginNestedDocsTranslations = GenericTranslationsObject + +export type PluginNestedDocsTranslationKeys = NestedKeysStripped diff --git a/packages/plugin-nested-docs/src/translations/languages/de.ts b/packages/plugin-nested-docs/src/translations/languages/de.ts new file mode 100644 index 00000000000..f33211c9607 --- /dev/null +++ b/packages/plugin-nested-docs/src/translations/languages/de.ts @@ -0,0 +1,13 @@ +import type { GenericTranslationsObject } from '@payloadcms/translations' + +export const de: GenericTranslationsObject = { + $schema: './translation-schema.json', + 'plugin-nested-docs': { + breadcrumbsPlural: 'Navigationspfade', + breadcrumbsSingular: 'Navigationspfad', + doc: 'Dokument', + label: 'Bezeichnung', + parent: 'Übergeordnetes Dokument', + url: 'URL', + }, +} diff --git a/packages/plugin-nested-docs/src/translations/languages/en.ts b/packages/plugin-nested-docs/src/translations/languages/en.ts new file mode 100644 index 00000000000..57bbcf0d85f --- /dev/null +++ b/packages/plugin-nested-docs/src/translations/languages/en.ts @@ -0,0 +1,13 @@ +import type { GenericTranslationsObject } from '@payloadcms/translations' + +export const en: GenericTranslationsObject = { + $schema: './translation-schema.json', + 'plugin-nested-docs': { + breadcrumbsPlural: 'Breadcrumbs', + breadcrumbsSingular: 'Breadcrumb', + doc: 'Document', + label: 'Label', + parent: 'Parent Document', + url: 'URL', + }, +} diff --git a/packages/plugin-nested-docs/src/translations/languages/it.ts b/packages/plugin-nested-docs/src/translations/languages/it.ts new file mode 100644 index 00000000000..e69ba43d539 --- /dev/null +++ b/packages/plugin-nested-docs/src/translations/languages/it.ts @@ -0,0 +1,13 @@ +import type { GenericTranslationsObject } from '@payloadcms/translations' + +export const it: GenericTranslationsObject = { + $schema: './translation-schema.json', + 'plugin-nested-docs': { + breadcrumbsPlural: 'Percorsi di navigazione', + breadcrumbsSingular: 'Percorso di navigazione', + doc: 'Documento', + label: 'Etichetta', + parent: 'Documento genitore', + url: 'URL', + }, +} diff --git a/packages/plugin-nested-docs/src/translations/translation-schema.json b/packages/plugin-nested-docs/src/translations/translation-schema.json new file mode 100644 index 00000000000..c685c5bc4a7 --- /dev/null +++ b/packages/plugin-nested-docs/src/translations/translation-schema.json @@ -0,0 +1,36 @@ +{ + "type": "object", + "$schema": "http://json-schema.org/draft-04/schema#", + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string" + }, + "plugin-nested-docs": { + "type": "object", + "additionalProperties": false, + "properties": { + "breadcrumbsPlural": { + "type": "string" + }, + "breadcrumbsSingular": { + "type": "string" + }, + "doc": { + "type": "string" + }, + "label": { + "type": "string" + }, + "parent": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": ["breadcrumbsPlural", "breadcrumbsSingular", "doc", "label", "parent", "url"] + } + }, + "required": ["plugin-nested-docs"] +} diff --git a/packages/plugin-nested-docs/src/translations/types.ts b/packages/plugin-nested-docs/src/translations/types.ts new file mode 100644 index 00000000000..97b072aed80 --- /dev/null +++ b/packages/plugin-nested-docs/src/translations/types.ts @@ -0,0 +1,3 @@ +import type { en } from './languages/en.js' + +export type PluginDefaultTranslationsObject = typeof en diff --git a/test/plugin-nested-docs/collections/Pages.ts b/test/plugin-nested-docs/collections/Pages.ts index f5ca5847726..75fcbc8446a 100644 --- a/test/plugin-nested-docs/collections/Pages.ts +++ b/test/plugin-nested-docs/collections/Pages.ts @@ -41,7 +41,7 @@ export const Pages: CollectionConfig = { }, admin: { components: { - Field: null, + Field: undefined, }, }, }, diff --git a/test/plugin-nested-docs/payload-types.ts b/test/plugin-nested-docs/payload-types.ts index 7dfbffb38c7..3b528d13fb5 100644 --- a/test/plugin-nested-docs/payload-types.ts +++ b/test/plugin-nested-docs/payload-types.ts @@ -128,6 +128,13 @@ export interface Page { title: string; slug: string; fullTitle?: string | null; + testArray?: + | { + testField?: string | null; + testField2?: string | null; + id?: string | null; + }[] + | null; parent?: (string | null) | Page; breadcrumbs?: | { @@ -278,6 +285,13 @@ export interface PagesSelect { title?: T; slug?: T; fullTitle?: T; + testArray?: + | T + | { + testField?: T; + testField2?: T; + id?: T; + }; parent?: T; breadcrumbs?: | T From 7fee1a765ecd570605abb103b1146d77d09d6d67 Mon Sep 17 00:00:00 2001 From: "maximilian.ebenkofler" Date: Sat, 30 May 2026 12:57:07 +0200 Subject: [PATCH 2/2] feat(plugin-nested-docs): configure package exports for translations Configures `package.json` exports to allow the main Payload application to discover and load the plugin's language-specific translation files. This completes the integration of the plugin's internationalization capabilities, enabling its UI labels to be properly localized. Additionally, corrects the relative `$schema` path within the translation files and promotes `@payloadcms/translations` to a runtime dependency. --- packages/plugin-nested-docs/package.json | 16 ++++++++++++++-- .../src/translations/languages/de.ts | 2 +- .../src/translations/languages/en.ts | 2 +- .../src/translations/languages/it.ts | 2 +- test/plugin-nested-docs/payload-types.ts | 14 -------------- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/plugin-nested-docs/package.json b/packages/plugin-nested-docs/package.json index 188168668c6..c573e3681ba 100644 --- a/packages/plugin-nested-docs/package.json +++ b/packages/plugin-nested-docs/package.json @@ -29,6 +29,16 @@ "import": "./src/exports/types.ts", "types": "./src/exports/types.ts", "default": "./src/exports/types.ts" + }, + "./translations/languages/all": { + "import": "./src/translations/index.ts", + "types": "./src/translations/index.ts", + "default": "./src/translations/index.ts" + }, + "./translations/languages/*": { + "import": "./src/translations/languages/*.ts", + "types": "./src/translations/languages/*.ts", + "default": "./src/translations/languages/*.ts" } }, "main": "./src/index.ts", @@ -46,11 +56,13 @@ "lint": "eslint .", "lint:fix": "eslint . --fix" }, - "devDependencies": { + "dependencies": { "@payloadcms/translations": "workspace:*", - "@payloadcms/eslint-config": "workspace:*", "payload": "workspace:*" }, + "devDependencies": { + "@payloadcms/eslint-config": "workspace:*" + }, "peerDependencies": { "payload": "workspace:*" }, diff --git a/packages/plugin-nested-docs/src/translations/languages/de.ts b/packages/plugin-nested-docs/src/translations/languages/de.ts index f33211c9607..579330e97b0 100644 --- a/packages/plugin-nested-docs/src/translations/languages/de.ts +++ b/packages/plugin-nested-docs/src/translations/languages/de.ts @@ -1,7 +1,7 @@ import type { GenericTranslationsObject } from '@payloadcms/translations' export const de: GenericTranslationsObject = { - $schema: './translation-schema.json', + $schema: '../translation-schema.json', 'plugin-nested-docs': { breadcrumbsPlural: 'Navigationspfade', breadcrumbsSingular: 'Navigationspfad', diff --git a/packages/plugin-nested-docs/src/translations/languages/en.ts b/packages/plugin-nested-docs/src/translations/languages/en.ts index 57bbcf0d85f..c7d8f3388ea 100644 --- a/packages/plugin-nested-docs/src/translations/languages/en.ts +++ b/packages/plugin-nested-docs/src/translations/languages/en.ts @@ -1,7 +1,7 @@ import type { GenericTranslationsObject } from '@payloadcms/translations' export const en: GenericTranslationsObject = { - $schema: './translation-schema.json', + $schema: '../translation-schema.json', 'plugin-nested-docs': { breadcrumbsPlural: 'Breadcrumbs', breadcrumbsSingular: 'Breadcrumb', diff --git a/packages/plugin-nested-docs/src/translations/languages/it.ts b/packages/plugin-nested-docs/src/translations/languages/it.ts index e69ba43d539..f68472f5a22 100644 --- a/packages/plugin-nested-docs/src/translations/languages/it.ts +++ b/packages/plugin-nested-docs/src/translations/languages/it.ts @@ -1,7 +1,7 @@ import type { GenericTranslationsObject } from '@payloadcms/translations' export const it: GenericTranslationsObject = { - $schema: './translation-schema.json', + $schema: '../translation-schema.json', 'plugin-nested-docs': { breadcrumbsPlural: 'Percorsi di navigazione', breadcrumbsSingular: 'Percorso di navigazione', diff --git a/test/plugin-nested-docs/payload-types.ts b/test/plugin-nested-docs/payload-types.ts index 3b528d13fb5..7dfbffb38c7 100644 --- a/test/plugin-nested-docs/payload-types.ts +++ b/test/plugin-nested-docs/payload-types.ts @@ -128,13 +128,6 @@ export interface Page { title: string; slug: string; fullTitle?: string | null; - testArray?: - | { - testField?: string | null; - testField2?: string | null; - id?: string | null; - }[] - | null; parent?: (string | null) | Page; breadcrumbs?: | { @@ -285,13 +278,6 @@ export interface PagesSelect { title?: T; slug?: T; fullTitle?: T; - testArray?: - | T - | { - testField?: T; - testField2?: T; - id?: T; - }; parent?: T; breadcrumbs?: | T