diff --git a/README.md b/README.md index e9932cc..fa16e51 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ## Features -- **Full spec from your config** — collections, globals, auth, versions, uploads, and jobs are documented with zero annotation. See the [endpoint list](https://payload-plugin-openapi.seshuk.im/v1/reference/endpoints). +- **Full spec from your config** — collections, globals, auth, versions, uploads, jobs, and the endpoints of official Payload plugins are documented with zero annotation. See the [endpoint list](https://payload-plugin-openapi.seshuk.im/v1/reference/endpoints). - **Interactive docs included** — mount Scalar or Swagger UI, or both on different paths. - **Native metadata** — document custom endpoints and refine field schemas through Payload's own `custom.openapi` key. No wrapper, no separate registry. - **Precise filtering** — choose exactly which entities and operations end up in the spec. @@ -31,7 +31,7 @@ ## Quick start -Requires **Payload 4.0.0-canary.38**, **Next.js 16.4 or later** and **Node.js 24.15 or later**. On Payload 3, use `@seshuk/payload-plugin-openapi@0`. +Requires **Payload 4.0.0-beta.0 or later**, **Next.js 16.4 or later** and **Node.js 24.15 or later**. On Payload 3, use `@seshuk/payload-plugin-openapi@0`. ### Install @@ -39,7 +39,7 @@ Requires **Payload 4.0.0-canary.38**, **Next.js 16.4 or later** and **Node.js 24 npm install @seshuk/payload-plugin-openapi@beta ``` -v1 is in beta under the `beta` npm tag, and Payload 4 is published under `canary`. +v1 is in beta under the `beta` npm tag, and Payload 4 is published under `beta`. ### Configure @@ -121,6 +121,10 @@ Full docs are at ****: - [CLI — `openapi:generate`](https://payload-plugin-openapi.seshuk.im/v1/cli/generate) - [Examples](https://payload-plugin-openapi.seshuk.im/v1/guides/examples) +## Official plugins + +When you install an official Payload plugin, the spec documents the REST endpoints it mounts: `@payloadcms/plugin-ecommerce`, `plugin-stripe`, `plugin-mcp`, `plugin-seo`, `plugin-search`, `plugin-multi-tenant`, `plugin-import-export` and `storage-r2`. There is nothing to configure. Filter them with `excludeOperations: [{ plugin: '@payloadcms/plugin-stripe' }]` or `{ kind: 'plugin' }`. See [Official plugins](https://payload-plugin-openapi.seshuk.im/v1/reference/endpoints#official-plugins). + ## For plugin authors If you maintain a Payload plugin, attach a `custom.openapi` Operation Object to the endpoints you add and they show up in the generated spec — no dependency on this package, and nothing for your users to wire up. The same key works on fields. If this plugin isn't installed, the metadata is inert. See [For plugin authors](https://payload-plugin-openapi.seshuk.im/v1/guides/plugin-authors). diff --git a/docs/v1/changelog.mdx b/docs/v1/changelog.mdx index da8ceaa..972501c 100644 --- a/docs/v1/changelog.mdx +++ b/docs/v1/changelog.mdx @@ -5,6 +5,25 @@ description: 'Release history for the OpenAPI plugin for Payload CMS.' Notable changes per release. For the full commit-level history, see the [GitHub releases](https://github.com/maximseshuk/payload-plugin-openapi/releases). + + +**Breaking changes** + +- Payload 4.0.0-beta.0 or later is required. +- An operation in extension `paths` now [replaces the generated operation](/v1/configuration/extensions#your-paths-take-priority) with the same path and method. Before, the two were deep-merged. +- `OperationKind` and `OperationContext.kind` can be `'plugin'`. An exhaustive `switch` over `kind` needs a new case. +- Endpoints of official Payload plugins now appear in the spec. `includeCustom: false` does not hide them. Add `{ kind: 'plugin' }` to `excludeOperations` to drop them. + +**New features** + +- Endpoints of the official ecommerce, Stripe, MCP, SEO, search, multi-tenant, import-export and R2 storage plugins are documented when the plugin is installed. See [Official plugins](/v1/reference/endpoints#official-plugins). +- `excludeOperations` rules take `plugin`, the package name of the plugin that added the operation. A custom endpoint sets it with `'x-payload-plugin'` in `custom.openapi`. See [Rules](/v1/configuration/filters#rules). +- `POST /api//{id}/rename` for upload collections, and the `version` query param on `GET /api//file/{filename}`. +- `POST /api/reorder` when a collection or join field is `orderable`. See [System](/v1/reference/endpoints#system). +- With `includeSystem`, the `payload-preferences` routes that get, save and delete a preference by key. See [Preferences](/v1/reference/endpoints#preferences). + + + **Breaking changes** diff --git a/docs/v1/configuration/extensions.mdx b/docs/v1/configuration/extensions.mdx index dab37c1..5f1ee96 100644 --- a/docs/v1/configuration/extensions.mdx +++ b/docs/v1/configuration/extensions.mdx @@ -37,11 +37,17 @@ openapi({ | Option | Type | Default | Description | | ------------ | ---------------------------------------------------- | ------- | -------------------------------------------------------------- | -| `paths` | `PathsObject` | — | Extra path items merged into the document's `paths` | +| `paths` | `PathsObject` | — | Extra path items merged into `paths`. See below | | `components` | `ComponentsObject` | — | Extra schemas, security schemes, etc. merged into `components` | | `tags` | `TagObject[]` | — | Extra tags appended to the document's `tags` | | `transform` | `({ doc, ...ctx }) => Document \| Promise` | — | Post-build hook over the finished document. It can be `async`. | +## Your paths take priority + +An operation in `paths` replaces the generated operation with the same path and method in full. Nothing from the generated operation is kept: not its `security`, `tags` or `responses`. Other methods on the same path stay. Path-level fields such as `summary` and `parameters` merge into the generated path item. + +To change one field of a generated operation, use [`custom.openapi`](/v1/guides/custom-endpoints) on the endpoint or a `transform`. + ## The `transform` hook `transform` receives one object, the `ExtensionContext`: the full built document as `doc` plus the build environment. It **must return the document** to serve. Mutate and return the same object, or return a new one — either works, but a missing `return` drops the document. It can be `async`. diff --git a/docs/v1/configuration/filters.mdx b/docs/v1/configuration/filters.mdx index 5c1a4ac..25599a9 100644 --- a/docs/v1/configuration/filters.mdx +++ b/docs/v1/configuration/filters.mdx @@ -22,6 +22,8 @@ description: 'Control what ends up in the spec: which entities are documented, w See [Endpoints](/v1/reference/endpoints) for the full list of endpoints and the option that controls each one. +`includeCustom` does not cover [official plugin](/v1/reference/endpoints#official-plugins) endpoints. To hide them, add `{ kind: 'plugin' }` to `excludeOperations`. + ## Choosing entities with `include` and `exclude` These two lists pick which collections and globals are documented. Each entry is an `EntityMatcher` — one of three shapes: @@ -104,7 +106,7 @@ filters: { ## Dropping individual operations -`include` / `exclude` work at the entity level. To remove specific operations — one method on one collection, every `DELETE`, anything under a path prefix — use `excludeOperations`. It applies to every operation in the spec: collection, global, custom, jobs and system (upload instructions, `/api/access`) operations. +`include` / `exclude` work at the entity level. To remove specific operations — one method on one collection, every `DELETE`, anything under a path prefix — use `excludeOperations`. It applies to every operation in the spec: collection, global, custom, jobs, system (upload instructions, `/api/access`) and [official plugin](/v1/reference/endpoints#official-plugins) operations. ### Rules @@ -116,8 +118,11 @@ Each rule is a set of conditions. Within one rule, every field you set must matc | `slug` | `string \| RegExp` | The collection or global slug, exact string or pattern; omit to match any | | `kind` | `OperationKind` | Restrict to one group; omit to match all | | `path` | `RegExp` | Tested against the final route path (e.g. `/api/posts/{id}`) | +| `plugin` | `string \| RegExp` | The plugin that added the operation, exact string or pattern | + +`kind` is one of `'collection'`, `'global'`, `'custom'` (endpoints with `custom.openapi`), `'jobs'`, `'system'` or `'plugin'` (endpoints of an official Payload plugin). Custom, jobs and system operations have no slug, so a rule with `slug` never matches them. A plugin operation mounted on a collection, such as `/api/search/reindex`, has that collection's slug. One mounted at the API root has none. -`kind` is one of `'collection'`, `'global'`, `'custom'` (endpoints with `custom.openapi`), `'jobs'` or `'system'`. Custom, jobs and system operations have no slug, so a rule with `slug` never matches them. +`plugin` is the plugin's package name, such as `'@payloadcms/plugin-search'`, for `'plugin'` operations. A rule with `plugin` never matches an operation without one. ```ts payload.config.ts filters: { @@ -128,13 +133,26 @@ filters: { { slug: 'posts', method: ['post', 'patch', 'put'] }, // Drop anything matching a path, regardless of method or entity. { path: /\/preview$/ }, + // Drop everything the Stripe plugin adds. + { plugin: '@payloadcms/plugin-stripe' }, ], } ``` +A custom endpoint can name its plugin too. Set `'x-payload-plugin'` in its `custom.openapi`. The operation keeps the `'custom'` kind, and rules and functions see the value as `plugin`. The key is removed from the final document. + +```ts my-plugin.ts +{ + path: '/my-plugin/sync', + method: 'post', + handler, + custom: { openapi: { 'x-payload-plugin': 'my-plugin', summary: 'Sync now', responses: { '200': { description: 'Done' } } } }, +} +``` + ### Functions -For logic that doesn't fit a rule, put a function in the same list. It receives the `method`, `path`, `slug` and `kind` of every operation. The operation is dropped only when the function returns `true`. An `async` function is awaited: +For logic that doesn't fit a rule, put a function in the same list. It receives the `method`, `path`, `slug`, `kind` and `plugin` of every operation. The operation is dropped only when the function returns `true`. An `async` function is awaited: ```ts payload.config.ts filters: { diff --git a/docs/v1/configuration/security-marking.mdx b/docs/v1/configuration/security-marking.mdx index 98696c7..922980a 100644 --- a/docs/v1/configuration/security-marking.mdx +++ b/docs/v1/configuration/security-marking.mdx @@ -73,7 +73,7 @@ openapi({ }) ``` -The function receives `{ method, path, slug, kind, detected }` for each operation. `kind` is `'collection'`, `'global'`, `'custom'`, `'jobs'` or `'system'`, and `slug` is set only for collections and globals. `detected` is the marking the plugin found, `'public'` or `'secured'`. The function can be `async`. +The function receives `{ method, path, slug, kind, plugin, detected }` for each operation. `kind` is `'collection'`, `'global'`, `'custom'`, `'jobs'`, `'system'` or `'plugin'`. `slug` is set for collections and globals, and for plugin operations mounted on a collection. `plugin` is the package name of the [official plugin](/v1/reference/endpoints#official-plugins) that added the operation, or the `x-payload-plugin` value of a custom endpoint. `detected` is the marking the plugin found, `'public'` or `'secured'`. The function can be `async`. When [interactive auth](/v1/configuration/interactive-auth) is on, every secured operation also lists the `PayloadLogin` scheme, so the docs UI sends the token it got from the login dialog. diff --git a/docs/v1/guides/plugin-authors.mdx b/docs/v1/guides/plugin-authors.mdx index fcb3334..c109399 100644 --- a/docs/v1/guides/plugin-authors.mdx +++ b/docs/v1/guides/plugin-authors.mdx @@ -62,6 +62,8 @@ export const myPlugin = That is the whole integration. When a user has the OpenAPI plugin installed, your endpoint appears in their spec under its own tag, and your field carries its description and example into the generated schemas. See [Custom endpoints](/v1/guides/custom-endpoints) for the operation shape and [Field metadata](/v1/guides/field-metadata) for everything fields support. +To let users filter your endpoints by plugin, add `'x-payload-plugin': 'my-plugin'` to each `custom.openapi`. A rule such as `{ plugin: 'my-plugin' }` in [`excludeOperations`](/v1/configuration/filters#rules) then drops them all. The key is removed from the final document. + ## Why plugin order does not matter The OpenAPI plugin does not read your config when it runs. It builds the document **lazily, on request, from the fully sanitized config**. By the time a request hits `/api/openapi.json`, every plugin in the `plugins` array has already run — so your endpoints, collections, and fields are visible no matter where either plugin sits in the list. There is no ordering constraint to document and nothing for your users to get wrong. diff --git a/docs/v1/introduction.mdx b/docs/v1/introduction.mdx index 58c6df3..eed2470 100644 --- a/docs/v1/introduction.mdx +++ b/docs/v1/introduction.mdx @@ -20,7 +20,7 @@ The document is built lazily from the fully sanitized config, so endpoints and f ## Requirements -Payload `4.0.0-canary.38`, Next.js 16.4 or later and Node.js 24.15 or later. v1 is in beta under the `beta` npm tag, and Payload 4 is published under `canary`. For Payload 3, use the `0.x` versions. +Payload `4.0.0-beta.0` or later, Next.js 16.4 or later and Node.js 24.15 or later. v1 is in beta under the `beta` npm tag, and Payload 4 is published under `beta`. For Payload 3, use the `0.x` versions. ## Where to go next diff --git a/docs/v1/quick-start.mdx b/docs/v1/quick-start.mdx index d5b06af..821fab2 100644 --- a/docs/v1/quick-start.mdx +++ b/docs/v1/quick-start.mdx @@ -24,8 +24,8 @@ yarn add @seshuk/payload-plugin-openapi@beta - Requires Payload `4.0.0-canary.38`, Next.js 16.4 or later and Node.js 24.15 or later. v1 is in beta under the `beta` - npm tag, and Payload 4 is published under `canary`. For Payload 3, use the `0.x` versions. + Requires Payload `4.0.0-beta.0` or later, Next.js 16.4 or later and Node.js 24.15 or later. v1 is in beta under the + `beta` npm tag, and Payload 4 is published under `beta`. For Payload 3, use the `0.x` versions. diff --git a/docs/v1/reference/endpoints.mdx b/docs/v1/reference/endpoints.mdx index 20c0f67..c39bab3 100644 --- a/docs/v1/reference/endpoints.mdx +++ b/docs/v1/reference/endpoints.mdx @@ -17,6 +17,7 @@ The plugin documents the REST endpoints that Payload 4 mounts for your config. A | `/api//{id}/validate` | POST | Always | — | | `/api//{id}/duplicate` | POST | `disableDuplicate` is not set (auth: off by default) | — | | `/api//file/{filename}` | GET | Upload collection | — | +| `/api//{id}/rename` | POST | Upload collection | — | | `/api//versions` | GET | `versions` is not `false` | `includeVersions` | | `/api//versions/{id}` | GET, POST (restore) | `versions` is not `false` | `includeVersions` | | `/api//access` | POST | Always | `includeAuth` + `includeAdminAuth` | @@ -52,13 +53,28 @@ The plugin documents the REST endpoints that Payload 4 mounts for your config. A ## System -| Endpoint | Method | When | Filter | -| ------------------------------------- | ----------- | --------------------------------- | ---------------------------------- | -| `/api/access` | GET | Always | `includeAuth` + `includeAdminAuth` | -| `/api/upload-instructions` | POST | At least one upload collection | — | -| `/api/upload-instructions/{uploadId}` | PUT, DELETE | At least one upload collection | — | -| `/api/payload-jobs/run` | GET | Tasks or workflows are configured | `includeJobs` | -| `/api/payload-jobs/handle-schedules` | GET | Tasks or workflows are configured | `includeJobs` | +| Endpoint | Method | When | Filter | +| ------------------------------------- | ----------- | ----------------------------------- | ---------------------------------- | +| `/api/access` | GET | Always | `includeAuth` + `includeAdminAuth` | +| `/api/upload-instructions` | POST | At least one upload collection | — | +| `/api/upload-instructions/{uploadId}` | PUT, DELETE | At least one upload collection | — | +| `/api/reorder` | POST | A collection or join is `orderable` | — | +| `/api/payload-jobs/run` | GET | Tasks or workflows are configured | `includeJobs` | +| `/api/payload-jobs/handle-schedules` | GET | Tasks or workflows are configured | `includeJobs` | + +`POST /api/reorder` is the drag-and-drop endpoint of `orderable` collections and orderable join fields. It is listed when Payload mounts it, and its `collectionSlug` is an enum of the documented collections that can be reordered. It needs a logged-in user with update access. A bad body returns `400` with `{ error }`, not the usual error shape. + +## Preferences + +The `payload-preferences` collection is hidden unless [`includeSystem`](/v1/configuration/filters) is on. With it, the spec lists the routes that store admin panel preferences for the current user: + +| Endpoint | Method | Does | +| ------------------------------- | ------ | ---------------------------------------------------------- | +| `/api/payload-preferences/{id}` | GET | Returns the preference, or `{ message, value: null }` | +| `/api/payload-preferences/{id}` | POST | Saves `value` from the body and returns `{ doc, message }` | +| `/api/payload-preferences/{id}` | DELETE | Deletes the preference and returns it with `message` | + +Here the `{id}` segment is the preference key, such as `nav`. Payload matches these routes before the standard ones, so `GET /count`, `POST /validate` and `POST /access` on this collection would read or save a preference named `count`, `validate` or `access`. The spec leaves them out. `PATCH /api/payload-preferences/{id}` still updates by document ID. Every route needs a logged-in user. ## Request and response bodies @@ -122,6 +138,90 @@ The spec also covers the fields that Payload adds: Payload fills `_h_slugPath` and `_h_titlePath` only when you ask for them. The `GET /api/` and `GET /api//{id}` operations of a folder or tag collection list a `computeHierarchyPaths` query param for this. Selecting either field with `select` computes them too. Both fields work in `sort`. If you rename them with `slugPathFieldName` or `titlePathFieldName`, the spec uses your names. +## Official plugins + +The spec documents the REST endpoints of these official Payload plugins. You don't need to set anything. The plugin finds an installed plugin by its `slug` in `config.plugins`, and the R2 adapter by its name in `config.storage`. It documents only the endpoints that are mounted, at the path they are mounted on. An endpoint with its own `custom.openapi` is documented from that instead. + +Each plugin gets one tag. With `nestedTags`, the tags sit under a `Plugins` group. A plugin tag with the same name as a collection tag, such as `Search`, stays under that collection. Rules and functions in `excludeOperations` and `security` see these operations with `kind: 'plugin'` and the package name as `plugin`. An endpoint mounted on a collection has that collection's `slug`, and the plugin documents it only when the collection passes your filters. See [Filters](/v1/configuration/filters#rules). + +### `@payloadcms/plugin-ecommerce` + +| Endpoint | Method | Auth | +| -------------------------------------- | ------ | ----------------------------------------- | +| `/api//{id}/add-item` | POST | Cart owner, or a guest cart with `secret` | +| `/api//{id}/remove-item` | POST | Cart owner, or a guest cart with `secret` | +| `/api//{id}/update-item` | POST | Cart owner, or a guest cart with `secret` | +| `/api//{id}/clear` | POST | Cart owner, or a guest cart with `secret` | +| `/api//{id}/merge` | POST | Logged in | +| `/api/payments//initiate` | POST | Optional. A guest sends `customerEmail` | +| `/api/payments//confirm-order` | POST | Optional. A guest sends `customerEmail` | +| `/api/payments/stripe/webhooks` | POST | None, called by Stripe | + +`` is the carts collection, found by its `/:id/add-item` endpoint. `` is the `name` of each payment adapter. The Stripe adapter adds `paymentIntentID` to the confirm body and `clientSecret` to the initiate response. + +### `@payloadcms/plugin-stripe` + +| Endpoint | Method | Auth | +| ---------------------- | ------ | ---------------------- | +| `/api/stripe/webhooks` | POST | None, called by Stripe | +| `/api/stripe/rest` | POST | `rest.access` | + +`/api/stripe/rest` is listed when `rest` is set. Its `stripeMethod` is an enum of `rest.allowedMethods`. + +### `@payloadcms/plugin-mcp` + +| Endpoint | Method | Auth | +| ---------- | ------ | ---------------------------------------------------- | +| `/api/mcp` | POST | Optional. The tools follow user access | +| `/api/mcp` | GET | None. Always `405`, the server sends no event stream | + +The body and response are JSON-RPC messages, single or batched. + +### `@payloadcms/plugin-seo` + +| Endpoint | Method | Auth | +| -------------------------------------- | ------ | --------- | +| `/api/plugin-seo/generate-title` | POST | Logged in | +| `/api/plugin-seo/generate-description` | POST | Logged in | +| `/api/plugin-seo/generate-url` | POST | Logged in | +| `/api/plugin-seo/generate-image` | POST | Logged in | + +Each returns `{ result }` from your generate function, or an empty string when you set none. + +### `@payloadcms/plugin-search` + +| Endpoint | Method | Auth | +| ----------------------- | ------ | -------------------------------------- | +| `/api//reindex` | POST | `update` and `delete` access on search | + +`` is `searchOverrides.slug`, `search` by default. The `collections` body field is an enum of the plugin's `collections`. + +### `@payloadcms/plugin-multi-tenant` + +| Endpoint | Method | Auth | +| ---------------------------------------- | ------ | --------- | +| `/api//populate-tenant-options` | GET | Logged in | + +`` is `tenantsSlug`, `tenants` by default. + +### `@payloadcms/plugin-import-export` + +| Endpoint | Method | Auth | +| ------------------------------ | ------ | --------- | +| `/api//download` | POST | Logged in | +| `/api//export-preview` | POST | Optional | +| `/api//preview-data` | POST | Optional | + +`` and `` are the export and import collections the plugin creates. The `collectionSlug` body field is an enum of the collections they serve. + +### `@payloadcms/storage-r2` + +| Endpoint | Method | Auth | +| ----------------------------------- | ------ | --------- | +| `/api/storage-r2-multi-part-upload` | POST | Logged in | + +Listed when `clientUploads` is on. One route takes three steps, chosen by the query params: start the upload, send each part as `application/octet-stream`, and complete it with the JSON list of parts. If another adapter already uses the path, the storage plugin adds a `-N` suffix, and the spec uses it. + ## Not documented These endpoints exist but are not in the spec. They serve the admin panel or are internal. @@ -129,7 +229,5 @@ These endpoints exist but are not in the spec. They serve the admin panel or are | Endpoint | Reason | | -------------------------------- | ----------------------------------------- | | `GET /api//paste-url/{id}` | Admin panel helper for `pasteURL` uploads | -| `POST /api/reorder` | Admin panel drag-and-drop for `orderable` | | `GET /api/og` | Admin panel Open Graph image (Next.js) | | `POST /api/graphql` | GraphQL, not REST | -| `/api/payload-preferences/{key}` | Admin panel preferences | diff --git a/docs/v1/reference/exports.mdx b/docs/v1/reference/exports.mdx index 871a055..2ed4934 100644 --- a/docs/v1/reference/exports.mdx +++ b/docs/v1/reference/exports.mdx @@ -49,10 +49,10 @@ All public types come from the same entry point, so editor autocomplete picks th | `LocalizedText` | A string, a language-keyed object or a `({ t, i18n }) => string` function | | `OpenApiVersion` | `'3.0' \| '3.1' \| '3.2'` | | `FilterOptions` | The `filters` object — entity include/exclude, endpoint-group toggles, operation rules ([Filters](/v1/configuration/filters)) | -| `OperationContext` | `{ method, path, slug, kind }` passed to `filters.excludeOperations` functions and `security` | -| `OperationRule` | One `filters.excludeOperations` rule — `method`, `slug`, `kind`, `path` | +| `OperationContext` | `{ method, path, slug, kind, plugin }` passed to `filters.excludeOperations` functions and `security` | +| `OperationRule` | One `filters.excludeOperations` rule — `method`, `slug`, `kind`, `path`, `plugin` | | `OperationFilter` | One `filters.excludeOperations` entry: an `OperationRule` or a function | -| `OperationKind` | `'collection' \| 'global' \| 'custom' \| 'jobs' \| 'system'` | +| `OperationKind` | `'collection' \| 'global' \| 'custom' \| 'jobs' \| 'system' \| 'plugin'` | | `SecurityOption` | The `security` function ([Security marking](/v1/configuration/security-marking)) | | `SecurityMarking` | `'public' \| 'secured'` | | `AccessOption` | The `access` function of `openapi()`, `scalar()` and `swaggerUi()` | diff --git a/docs/v1/upgrade-guide.mdx b/docs/v1/upgrade-guide.mdx index 7284905..5dcb599 100644 --- a/docs/v1/upgrade-guide.mdx +++ b/docs/v1/upgrade-guide.mdx @@ -3,7 +3,7 @@ title: 'Upgrade guide' description: 'Upgrade the OpenAPI plugin from 0.x to v1: renamed options, entity security values and changed defaults.' --- -This guide covers upgrading from 0.x to v1. v1 needs Payload `4.0.0-canary.38` and Next.js 16.4 or later. +This guide covers upgrading from 0.x to v1. v1 needs Payload `4.0.0-beta.0` or later and Next.js 16.4 or later. ## 0.x to v1 @@ -63,5 +63,6 @@ Some defaults changed too: - The spec no longer takes `servers` from the request `Host` header, because a client can fake it. It uses `serverURL` from your Payload config, or `servers`. To keep the old behavior for known hosts, list them in [`trustedHosts`](/v1/configuration/info#trusted-hosts). - An extension `transform` that throws now fails the spec request. Before, the plugin logged the error and served the document without that transform. +- An operation in extension `paths` now [replaces the generated operation](/v1/configuration/extensions#your-paths-take-priority) with the same path and method. Before, the two were deep-merged. To change one field of a generated operation, use `custom.openapi` on the endpoint or a `transform`. - The docs UI loads a pinned library version with [Subresource Integrity](/v1/configuration/docs-ui#pinned-versions-and-sri). -- Upload read schemas show `variants` instead of `sizes`, because Payload renamed image sizes to variants. See the [Payload release notes](https://github.com/payloadcms/payload/releases/tag/v4.0.0-canary.38). +- Upload read schemas show `variants` instead of `sizes`, because Payload renamed image sizes to variants. See the [Payload v4 migration guide](https://payloadcms.com/docs/beta/migration-guide/v4#upload-image-data-now-uses-variants). diff --git a/package.json b/package.json index af4f484..3fc6a11 100644 --- a/package.json +++ b/package.json @@ -74,10 +74,18 @@ "@scalar/openapi-types": "^0.9.7" }, "devDependencies": { - "@payloadcms/db-sqlite": "4.0.0-canary.38", - "@payloadcms/next": "4.0.0-canary.38", - "@payloadcms/translations": "4.0.0-canary.38", - "@payloadcms/ui": "4.0.0-canary.38", + "@payloadcms/db-sqlite": "4.0.0-beta.0", + "@payloadcms/next": "4.0.0-beta.0", + "@payloadcms/plugin-ecommerce": "4.0.0-beta.0", + "@payloadcms/plugin-import-export": "4.0.0-beta.0", + "@payloadcms/plugin-mcp": "4.0.0-beta.0", + "@payloadcms/plugin-multi-tenant": "4.0.0-beta.0", + "@payloadcms/plugin-search": "4.0.0-beta.0", + "@payloadcms/plugin-seo": "4.0.0-beta.0", + "@payloadcms/plugin-stripe": "4.0.0-beta.0", + "@payloadcms/storage-r2": "4.0.0-beta.0", + "@payloadcms/translations": "4.0.0-beta.0", + "@payloadcms/ui": "4.0.0-beta.0", "@seriousme/openapi-schema-validator": "^2.9.0", "@seshuk/payload-plugin-tooling": "0.2.3", "@types/node": "catalog:", @@ -86,7 +94,7 @@ "next": "16.4.0", "oxfmt": "^0.72.0", "oxlint": "^1.86.0", - "payload": "4.0.0-canary.38", + "payload": "4.0.0-beta.0", "react": "catalog:", "react-dom": "catalog:", "tsdown": "^0.23.0", @@ -94,8 +102,8 @@ "vitest": "^5.0.3" }, "peerDependencies": { - "@payloadcms/translations": "4.0.0-canary.38", - "payload": "4.0.0-canary.38" + "@payloadcms/translations": "^4.0.0-beta.0", + "payload": "^4.0.0-beta.0" }, "devEngines": { "packageManager": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 034f64d..9fd0b93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,23 +186,47 @@ importers: version: 0.9.7 devDependencies: '@payloadcms/db-sqlite': - specifier: 4.0.0-canary.38 - version: 4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2)) + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)) '@payloadcms/next': - specifier: 4.0.0-canary.38 - version: 4.0.0-canary.38(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/plugin-ecommerce': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/plugin-import-export': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@payloadcms/ui@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@payloadcms/plugin-mcp': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)) + '@payloadcms/plugin-multi-tenant': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@payloadcms/ui@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)) + '@payloadcms/plugin-search': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/plugin-seo': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/plugin-stripe': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/storage-r2': + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) '@payloadcms/translations': - specifier: 4.0.0-canary.38 - version: 4.0.0-canary.38 + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0 '@payloadcms/ui': - specifier: 4.0.0-canary.38 - version: 4.0.0-canary.38(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) '@seriousme/openapi-schema-validator': specifier: ^2.9.0 version: 2.11.0 '@seshuk/payload-plugin-tooling': specifier: 0.2.3 - version: 0.2.3(@payloadcms/db-sqlite@4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2)))(oxfmt@0.72.0)(oxlint@1.87.0)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.23.15)(typescript@7.0.2))(vitest@5.0.3) + version: 0.2.3(@payloadcms/db-sqlite@4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)))(oxfmt@0.72.0)(oxlint@1.87.0)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.23.15)(typescript@7.0.2))(vitest@5.0.3) '@types/node': specifier: 'catalog:' version: 26.6.4 @@ -222,8 +246,8 @@ importers: specifier: ^1.86.0 version: 1.87.0 payload: - specifier: 4.0.0-canary.38 - version: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + specifier: 4.0.0-beta.0 + version: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) react: specifier: 'catalog:' version: 19.3.0 @@ -333,6 +357,88 @@ packages: '@asyncapi/specs@6.11.1': resolution: {integrity: sha512-A3WBLqAKGoJ2+6FWFtpjBlCQ1oFCcs4GxF7zsIGvNqp/klGUHjlA3aAcZ9XMMpLGE8zPeYDz2x9FmO6DSuKraQ==} + '@aws-sdk/checksums@3.1001.1': + resolution: {integrity: sha512-x12Q17KYlJAd3nKf8LV5LV0vt8sh8/6YfQLGPtrGnQf/tW4jqxPGq5GPpuVitpQYM3eUR4XB7CbxZf751NMbLw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1148.0': + resolution: {integrity: sha512-d2AcfJCZTi4w5zJZD7H/XeVRgJy1vOD5EfO1Y3pKAwfp8pWz2dyJ8Q3vOJFbRiTIQF7c4mB+aptNUyCE4i4kqw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.978.1': + resolution: {integrity: sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.72': + resolution: {integrity: sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.74': + resolution: {integrity: sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.973.17': + resolution: {integrity: sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.79': + resolution: {integrity: sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.84': + resolution: {integrity: sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.72': + resolution: {integrity: sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.973.16': + resolution: {integrity: sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.78': + resolution: {integrity: sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/lib-storage@3.1148.0': + resolution: {integrity: sha512-PhgvDNYhkRcMfVTn1km7Sbyext/6Mbm6bZmlBhCdp5haQ++PvohOFTzyaprpxrLpFu3mCWTUUcRoCZgqjOJIHQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@aws-sdk/client-s3': ^3.1148.0 + + '@aws-sdk/middleware-sdk-s3@3.972.77': + resolution: {integrity: sha512-E7W2UOeUoc+lg3uIfR/dM7ZwusHwhBQrKMnlkRv4EXRR+C0YtV1pg25xC7GdZIhXH+NAMgZPCbE7o5to2cjFiw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.46': + resolution: {integrity: sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/s3-request-presigner@3.1148.0': + resolution: {integrity: sha512-k9UM9y4fSUl6QzO2tluO2/MzQRghVhF3kbQ2DXAcZAEECvarGa8zxI4oTEq2VHthhNyYuBuOmLvGD8EH/1vtTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.47': + resolution: {integrity: sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1138.0': + resolution: {integrity: sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.974.6': + resolution: {integrity: sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.41': + resolution: {integrity: sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -1725,6 +1831,10 @@ packages: '@mintlify/validation@0.1.894': resolution: {integrity: sha512-07yuosEZEqCu1YmEhesbhKnsxoltIg8lovVtW+Jvis0oREM00vJSA1EsF4sXMkFcaP8vEB56hT5/Qx+zfhhkpg==} + '@modelcontextprotocol/core@2.3.1': + resolution: {integrity: sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==} + engines: {node: '>=20'} + '@modelcontextprotocol/sdk@1.30.0': resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} engines: {node: '>=18'} @@ -1735,6 +1845,10 @@ packages: '@cfworker/json-schema': optional: true + '@modelcontextprotocol/server@2.3.1': + resolution: {integrity: sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==} + engines: {node: '>=20'} + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -2222,39 +2336,102 @@ packages: cpu: [x64] os: [win32] - '@payloadcms/db-sqlite@4.0.0-canary.38': - resolution: {integrity: sha512-4cIMI6XscQaSimiX6J+yEjsIVcdzkaXSGq9HbvyXqoyKWRkDkAqQrJ+aEmQ2PpTQICnFGXeOH1EK57C/5XYhPA==} + '@payloadcms/db-sqlite@4.0.0-beta.0': + resolution: {integrity: sha512-MlQBlPbIg/tMFGXEzfuP+xZGm3vMebuMXTYVSgzizpA2hZQZbZAowkM2MKqpyfN8s2BfzL1DB9Oz/JlcOD4JDg==} peerDependencies: - payload: 4.0.0-canary.38 + payload: 4.0.0-beta.0 - '@payloadcms/drizzle@4.0.0-canary.38': - resolution: {integrity: sha512-IntAuQqgNzor1ZvzlhD0Bb9w8XrUWPYH62UKYhDVUcA00XFs6etkggeGt4iULweM7sMZZI82dm0syJ6JfT6eHA==} + '@payloadcms/drizzle@4.0.0-beta.0': + resolution: {integrity: sha512-UuOLOENnJSt5AcHxtDap0XUiJwJ7sOdTSVQMA+jmasf7IsBoiPXohfO+bUj05hm9eU6QWzvK8Psp5PDsqSA/dQ==} peerDependencies: - payload: 4.0.0-canary.38 + payload: 4.0.0-beta.0 - '@payloadcms/graphql@4.0.0-canary.38': - resolution: {integrity: sha512-JZvndTmx1pnVM95HEDEba1zGHVo2SuQwbMz37GlJ+gdDuIVktTLLQBP8gTrRLl9stdf3RL/h6oeA7uxEinkvmw==} + '@payloadcms/graphql@4.0.0-beta.0': + resolution: {integrity: sha512-w+eavvxipQjhjXbsmiR25aiggmp/o/CJ3kDQ/wyvLSrW8HwIxbX0s8rUcGo2RtezGJ1F2LQdZdhNeQBLhGlWpA==} hasBin: true peerDependencies: graphql: ^16.8.1 - payload: 4.0.0-canary.38 + payload: 4.0.0-beta.0 - '@payloadcms/next@4.0.0-canary.38': - resolution: {integrity: sha512-AJrDVOVw4V1gqDvCYCDnEd10quw7rHtRHaCRe6tYWtkyB3s3lcRveuM0mPWxmNFWMeYAgkhp57doDV18Psn66A==} + '@payloadcms/next@4.0.0-beta.0': + resolution: {integrity: sha512-KHGJnqgMJtLCOVF1w6axTCmm/B+RYq8jgkCTSnW11QKeoLSu+71V4RpzAnXJk0onq5JYtvw1imzEfP4Z82jo4g==} engines: {node: '>=24.15.0'} peerDependencies: graphql: ^16.8.1 next: '>=16.4.0 <17.0.0' - payload: 4.0.0-canary.38 + payload: 4.0.0-beta.0 + + '@payloadcms/plugin-cloud-storage@4.0.0-beta.0': + resolution: {integrity: sha512-pg2ISlahabJzBEUbbMi9fPmEr0gGY/aQR73W3tX8asOwfV561vT1DrK2ZubNDOu5ey080WMk8wgU4ErkJL5nTw==} + peerDependencies: + payload: 4.0.0-beta.0 + react: ^19.0.1 || ^19.1.2 || ^19.2.1 + react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 + + '@payloadcms/plugin-ecommerce@4.0.0-beta.0': + resolution: {integrity: sha512-Q/Q4I7Gcd2V0pYXOIH5Wx/75J3WyepTLf8teshRLiRhhX37kcS2Divy9I0pA1VjGRkJ4nOYPT4RQUzwOXbwFtg==} + peerDependencies: + payload: 4.0.0-beta.0 + react: ^19.0.1 || ^19.1.2 || ^19.2.1 + react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 + + '@payloadcms/plugin-import-export@4.0.0-beta.0': + resolution: {integrity: sha512-NOMm0nfXoHdZ6lVJRz2Sv3ooWfNvoEFOJ8L3MUksmrydzxbGZp9UvNDJZIjSkjAk6hFbIzp/aAEwlSU1NRPPOQ==} + peerDependencies: + '@payloadcms/ui': 4.0.0-beta.0 + payload: 4.0.0-beta.0 + + '@payloadcms/plugin-mcp@4.0.0-beta.0': + resolution: {integrity: sha512-5zA/cZ6ktPUUceBKMrY/7VrrVC1pRjQZJaET7tjQqVNgIsXmw5ywxpNgz2C1M9SI6V34h2V166VjYCfkc1+gQg==} + hasBin: true + peerDependencies: + payload: 4.0.0-beta.0 + + '@payloadcms/plugin-multi-tenant@4.0.0-beta.0': + resolution: {integrity: sha512-2DlA3RVfRlLTTMDhyKxvxz9ld/VXCMJY6dAO+f8KD5fX3DYbzGJtc9rWDSYIhCIZfR7uNJTDFCIiT2oFYIstFw==} + peerDependencies: + '@payloadcms/ui': 4.0.0-beta.0 + payload: 4.0.0-beta.0 + + '@payloadcms/plugin-search@4.0.0-beta.0': + resolution: {integrity: sha512-qsF2KXIZ0OyuALD/LHj6zaas8wlNaT4CKVj7wZqFtFJ5qes26d48b/28avCxj6aKEctsovgo/tsLLTl1ZW7yxQ==} + peerDependencies: + payload: 4.0.0-beta.0 + react: ^19.0.1 || ^19.1.2 || ^19.2.1 + react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 + + '@payloadcms/plugin-seo@4.0.0-beta.0': + resolution: {integrity: sha512-jwtTrHEow4PdskNnPkWoCS+0yhpX2mtARi81VTKOtW8aSL67NlWvZvutwcdZfWFYeovc15YdXci7GsOwJ2nowA==} + peerDependencies: + payload: 4.0.0-beta.0 + react: ^19.0.1 || ^19.1.2 || ^19.2.1 + react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 - '@payloadcms/translations@4.0.0-canary.38': - resolution: {integrity: sha512-7ao8otvtNFYVr13uZvZqr5LiP7ey4DdczL0ikuPi8LvM3QBo7WklCxgK5YrFt8WSAhwZamDNY1V73DGQmAPWjw==} + '@payloadcms/plugin-stripe@4.0.0-beta.0': + resolution: {integrity: sha512-jKi/65i0Jsmdb7nhdgZlXT8AqJnb2tp35KOhhNX/9+8q0816fGpUhDI7UM8l1+r+3Tm7OzEY0epQBvh/PECHSg==} + peerDependencies: + payload: 4.0.0-beta.0 - '@payloadcms/ui@4.0.0-canary.38': - resolution: {integrity: sha512-+NIwg3zX+U+O8Bsz1vUhx+raAKn1Dgj437c85Vs/IbOv6wq41Rv2+iAS+yMaB+Uu0kcCiYxAeJC2wbw6oYW47w==} + '@payloadcms/storage-r2@4.0.0-beta.0': + resolution: {integrity: sha512-mI0zASiusiZAkIOinLxQiHEb2epR50xm+XAUXp1YMmNztyVei+OW7yBbhoaiiqXGZ7usRwGNB8et4NIyYtTaAQ==} engines: {node: '>=24.15.0'} peerDependencies: - payload: 4.0.0-canary.38 + payload: 4.0.0-beta.0 + + '@payloadcms/storage-s3@4.0.0-beta.0': + resolution: {integrity: sha512-vBWPBepQxanmv0RD81d68RY+h/pK25dUe9hZzI5bj6eXvOZIV1YOGn3KOjnb0+El8xLbni16gh2rJ6MyFzz4zA==} + engines: {node: '>=24.15.0'} + peerDependencies: + payload: 4.0.0-beta.0 + + '@payloadcms/translations@4.0.0-beta.0': + resolution: {integrity: sha512-/7xz7eBWYvQFiUu9v8CJM2RZpISYvu7xPtBiQ7PYx1xwHOb409W6SB1toEZHLeIpUvEsQaF+J4G4M1P5/D+P9Q==} + + '@payloadcms/ui@4.0.0-beta.0': + resolution: {integrity: sha512-EnV4VlluR6DZUfkkfCpbQE62xGoSnsT0MhG1T7lmvMtUoOnbJ7nAtcGBtsS0n1j+G8cTl8D4J/u5IaGR5gbBTw==} + engines: {node: '>=24.15.0'} + peerDependencies: + payload: 4.0.0-beta.0 react: ^19.0.1 || ^19.1.2 || ^19.2.1 react-dom: ^19.0.1 || ^19.1.2 || ^19.2.1 @@ -2600,6 +2777,30 @@ packages: resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} engines: {node: '>=12'} + '@smithy/core@3.35.2': + resolution: {integrity: sha512-ejTukBuP/Pg3t9N8Ux+0zKsXeHWm7p6W3HbXFOyltKasSxZwCFmvTut7kBdZSCF3m9y1bXXyVu6p2xuMFcsTyQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.5.2': + resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.8.0': + resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.12.1': + resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.7.4': + resolution: {integrity: sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.19.0': + resolution: {integrity: sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==} + engines: {node: '>=18.0.0'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -3335,6 +3536,9 @@ packages: body-scroll-lock@4.0.0-beta.0: resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==} + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + brace-expansion@1.1.18: resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} @@ -3351,6 +3555,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -3623,6 +3830,12 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + csv-parse@5.6.0: + resolution: {integrity: sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q==} + + csv-stringify@6.5.2: + resolution: {integrity: sha512-RFPahj0sXcmUyjrObAK+DOWtMvMIFV328n4qZJhgX3x2RqkQgOTU2mCUmiFR0CzM6AzChlRSUErjiJeEt8BaQA==} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -3747,6 +3960,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -4079,6 +4296,10 @@ packages: events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + eventsource-parser@3.1.1: resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} engines: {node: '>=18.0.0'} @@ -4091,6 +4312,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + expect-type@1.4.0: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} @@ -4198,9 +4423,16 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + find-node-modules@2.1.3: + resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} + find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + findup-sync@4.0.0: + resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} + engines: {node: '>= 8'} + focus-trap@7.5.4: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} @@ -4346,6 +4578,14 @@ packages: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -4476,6 +4716,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + hono@4.13.13: resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} engines: {node: '>=16.9.0'} @@ -4497,10 +4741,6 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - http-status@2.1.0: - resolution: {integrity: sha512-O5kPr7AW7wYd/BBiOezTwnVAnmSNFY+J7hlZD2X5IOxVBetjcHAiTXhzj0gMrnojQlwy+UT1/Y3H3vJ3UlmvLA==} - engines: {node: '>= 0.4.0'} - http2-wrapper@2.2.1: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} @@ -4785,6 +5025,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -4799,8 +5043,8 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - jose@6.2.3: - resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + jose@6.2.12: + resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==} jose@6.2.9: resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==} @@ -4914,6 +5158,10 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + lodash.topath@4.5.2: resolution: {integrity: sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==} @@ -5045,6 +5293,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + merge@2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -5482,6 +5733,10 @@ packages: parse-latin@7.0.0: resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -5521,8 +5776,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - payload@4.0.0-canary.38: - resolution: {integrity: sha512-PJMpPqTIsd4DIOyVo3aYVETfvOfsfVd7PfqwJjnbCCqv0Zafzx/H0zACJbhMt1N5CtT5ID4HL6SnNmLWMsgqBQ==} + payload@4.0.0-beta.0: + resolution: {integrity: sha512-eOFGdWPyGMlghjJ3sys4JqRJBEP1NNElOenUvXddkHU39LwqSrnJYrvERI3cuD4duxz7VYPePo52giGcctsMbA==} engines: {node: '>=24.15.0'} hasBin: true peerDependencies: @@ -5916,6 +6171,10 @@ packages: resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -6242,6 +6501,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + stream-browserify@3.0.0: + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -6291,6 +6553,10 @@ packages: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} + stripe@10.17.0: + resolution: {integrity: sha512-JHV2KoL+nMQRXu3m9ervCZZvi4DDCJfzHUE6CmtJxR9TmizyYfrVuhGvnsZLLnheby9Qrnf4Hq6iOEcejGwnGQ==} + engines: {node: ^8.1 || >=10.*} + strnum@2.4.2: resolution: {integrity: sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==} @@ -6823,6 +7089,10 @@ packages: resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -7056,6 +7326,190 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 + '@aws-sdk/checksums@3.1001.1': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1148.0': + dependencies: + '@aws-sdk/checksums': 3.1001.1 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/middleware-sdk-s3': 3.972.77 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/core@3.978.1': + dependencies: + '@aws-sdk/types': 3.974.6 + '@aws-sdk/xml-builder': 3.972.41 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.35.2 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.72': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.74': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.17': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/credential-provider-env': 3.972.72 + '@aws-sdk/credential-provider-http': 3.972.74 + '@aws-sdk/credential-provider-login': 3.972.79 + '@aws-sdk/credential-provider-process': 3.972.72 + '@aws-sdk/credential-provider-sso': 3.973.16 + '@aws-sdk/credential-provider-web-identity': 3.972.78 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.79': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.84': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.72 + '@aws-sdk/credential-provider-http': 3.972.74 + '@aws-sdk/credential-provider-ini': 3.973.17 + '@aws-sdk/credential-provider-process': 3.972.72 + '@aws-sdk/credential-provider-sso': 3.973.16 + '@aws-sdk/credential-provider-web-identity': 3.972.78 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.72': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.16': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/token-providers': 3.1138.0 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.78': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/lib-storage@3.1148.0(@aws-sdk/client-s3@3.1148.0)': + dependencies: + '@aws-sdk/client-s3': 3.1148.0 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + buffer: 5.6.0 + events: 3.3.0 + stream-browserify: 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.77': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.46': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1148.0': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.47': + dependencies: + '@aws-sdk/types': 3.974.6 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1138.0': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/types@3.974.6': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.41': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.3.0': {} + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -8377,6 +8831,10 @@ snapshots: - supports-color - typescript + '@modelcontextprotocol/core@2.3.1': + dependencies: + zod: 4.6.5 + '@modelcontextprotocol/sdk@1.30.0(zod@4.3.6)': dependencies: '@hono/node-server': 2.1.3(hono@4.13.13) @@ -8400,6 +8858,11 @@ snapshots: - supports-color optional: true + '@modelcontextprotocol/server@2.3.1': + dependencies: + '@modelcontextprotocol/core': 2.3.1 + zod: 4.6.5 + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 @@ -8674,14 +9137,14 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.87.0': optional: true - '@payloadcms/db-sqlite@4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))': + '@payloadcms/db-sqlite@4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))': dependencies: '@libsql/client': 0.14.0 - '@payloadcms/drizzle': 4.0.0-canary.38(@libsql/client@0.14.0)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2)) + '@payloadcms/drizzle': 4.0.0-beta.0(@libsql/client@0.14.0)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)) console-table-printer: 2.15.0 drizzle-kit: 0.31.7 drizzle-orm: 0.45.2(@libsql/client@0.14.0) - payload: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) prompts: 2.4.2 to-snake-case: 1.0.0 uuid: 14.0.0 @@ -8718,12 +9181,12 @@ snapshots: - supports-color - utf-8-validate - '@payloadcms/drizzle@4.0.0-canary.38(@libsql/client@0.14.0)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))': + '@payloadcms/drizzle@4.0.0-beta.0(@libsql/client@0.14.0)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))': dependencies: console-table-printer: 2.15.0 dequal: 2.0.3 drizzle-orm: 0.45.2(@libsql/client@0.14.0) - payload: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) prompts: 2.4.2 to-snake-case: 1.0.0 uuid: 14.0.0 @@ -8758,35 +9221,34 @@ snapshots: - sql.js - sqlite3 - '@payloadcms/graphql@4.0.0-canary.38(graphql@16.11.0)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(typescript@7.0.2)': + '@payloadcms/graphql@4.0.0-beta.0(graphql@16.11.0)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(typescript@7.0.2)': dependencies: graphql: 16.11.0 graphql-scalars: 1.22.2(graphql@16.11.0) - payload: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) pluralize: 8.0.0 ts-essentials: 10.0.3(typescript@7.0.2) tsx: 4.23.15 transitivePeerDependencies: - typescript - '@payloadcms/next@4.0.0-canary.38(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + '@payloadcms/next@4.0.0-beta.0(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': dependencies: '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) - '@payloadcms/graphql': 4.0.0-canary.38(graphql@16.11.0)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(typescript@7.0.2) - '@payloadcms/translations': 4.0.0-canary.38 - '@payloadcms/ui': 4.0.0-canary.38(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/graphql': 4.0.0-beta.0(graphql@16.11.0)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(typescript@7.0.2) + '@payloadcms/translations': 4.0.0-beta.0 + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) busboy: 1.6.0 dequal: 2.0.3 file-type: 22.0.1 graphql: 16.11.0 graphql-http: 1.22.4(graphql@16.11.0) graphql-playground-html: 1.6.30 - http-status: 2.1.0 next: 16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4) path-to-regexp: 6.3.0 - payload: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) qs-esm: 8.0.1 sass: 1.77.4 uuid: 14.0.0 @@ -8799,11 +9261,143 @@ snapshots: - supports-color - typescript - '@payloadcms/translations@4.0.0-canary.38': + '@payloadcms/plugin-cloud-storage@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + find-node-modules: 2.1.3 + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + range-parser: 1.2.1 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - monaco-editor + - supports-color + - typescript + + '@payloadcms/plugin-ecommerce@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@payloadcms/translations': 4.0.0-beta.0 + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + qs-esm: 8.0.1 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - monaco-editor + - supports-color + - typescript + + '@payloadcms/plugin-import-export@4.0.0-beta.0(@payloadcms/ui@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + '@faceless-ui/modal': 3.0.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@payloadcms/translations': 4.0.0-beta.0 + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + csv-parse: 5.6.0 + csv-stringify: 6.5.2 + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + qs-esm: 8.0.1 + transitivePeerDependencies: + - react + - react-dom + + '@payloadcms/plugin-mcp@4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))': + dependencies: + '@modelcontextprotocol/server': 2.3.1 + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + tsx: 4.23.15 + + '@payloadcms/plugin-multi-tenant@4.0.0-beta.0(@payloadcms/ui@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))': + dependencies: + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + + '@payloadcms/plugin-search@4.0.0-beta.0(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@payloadcms/next': 4.0.0-beta.0(@types/react@19.2.15)(graphql@16.11.0)(monaco-editor@0.55.1)(next@16.4.0(@types/node@26.6.4)(babel-plugin-macros@3.1.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - graphql + - monaco-editor + - next + - supports-color + - typescript + + '@payloadcms/plugin-seo@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@payloadcms/translations': 4.0.0-beta.0 + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - monaco-editor + - supports-color + - typescript + + '@payloadcms/plugin-stripe@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@payloadcms/translations': 4.0.0-beta.0 + '@payloadcms/ui': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + lodash.get: 4.4.2 + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + stripe: 10.17.0 + uuid: 14.0.0 + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - monaco-editor + - react + - react-dom + - supports-color + - typescript + + '@payloadcms/storage-r2@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@aws-sdk/client-s3': 3.1148.0 + '@payloadcms/plugin-cloud-storage': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@payloadcms/storage-s3': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - monaco-editor + - react + - react-dom + - supports-color + - typescript + + '@payloadcms/storage-s3@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@aws-sdk/client-s3': 3.1148.0 + '@aws-sdk/lib-storage': 3.1148.0(@aws-sdk/client-s3@3.1148.0) + '@aws-sdk/s3-request-presigner': 3.1148.0 + '@payloadcms/plugin-cloud-storage': 4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) + transitivePeerDependencies: + - '@types/react' + - date-fns-tz + - monaco-editor + - react + - react-dom + - supports-color + - typescript + + '@payloadcms/translations@4.0.0-beta.0': dependencies: date-fns: 4.4.0 - '@payloadcms/ui@4.0.0-canary.38(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + '@payloadcms/ui@4.0.0-beta.0(@types/react@19.2.15)(monaco-editor@0.55.1)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': dependencies: '@date-fns/tz': 1.2.0 '@dnd-kit/core': 6.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -8815,7 +9409,7 @@ snapshots: '@faceless-ui/window-info': 3.0.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@floating-ui/dom': 1.7.6 '@monaco-editor/react': 4.7.0(monaco-editor@0.55.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@payloadcms/translations': 4.0.0-canary.38 + '@payloadcms/translations': 4.0.0-beta.0 '@swc/helpers': 0.5.23 bson-objectid: 2.0.4 date-fns: 4.4.0 @@ -8823,7 +9417,7 @@ snapshots: md5: 2.3.0 object-to-formdata: 4.5.1 path-to-regexp: 6.3.0 - payload: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) qs-esm: 8.0.1 react: 19.3.0 react-datepicker: 9.1.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -9001,12 +9595,12 @@ snapshots: ajv-formats: 3.0.1 yaml: 2.9.1 - '@seshuk/payload-plugin-tooling@0.2.3(@payloadcms/db-sqlite@4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2)))(oxfmt@0.72.0)(oxlint@1.87.0)(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2))(tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.23.15)(typescript@7.0.2))(vitest@5.0.3)': + '@seshuk/payload-plugin-tooling@0.2.3(@payloadcms/db-sqlite@4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)))(oxfmt@0.72.0)(oxlint@1.87.0)(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2))(tsdown@0.23.0(oxc-resolver@11.20.0)(tsx@4.23.15)(typescript@7.0.2))(vitest@5.0.3)': optionalDependencies: - '@payloadcms/db-sqlite': 4.0.0-canary.38(payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2)) + '@payloadcms/db-sqlite': 4.0.0-beta.0(payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2)) oxfmt: 0.72.0 oxlint: 1.87.0 - payload: 4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2) + payload: 4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2) tsdown: 0.23.0(oxc-resolver@11.20.0)(tsx@4.23.15)(typescript@7.0.2) vitest: 5.0.3(@types/node@26.6.4)(@vitest/coverage-v8@5.0.3)(vite@7.3.3(@types/node@26.6.4)(jiti@1.21.7)(sass@1.77.4)(tsx@4.23.15)(yaml@2.9.1)) @@ -9068,6 +9662,39 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 + '@smithy/core@3.35.2': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.5.2': + dependencies: + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.8.0': + dependencies: + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.12.1': + dependencies: + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.7.4': + dependencies: + '@smithy/core': 3.35.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/types@4.19.0': + dependencies: + tslib: 2.8.1 + '@socket.io/component-emitter@3.1.2': {} '@stablelib/base64@1.0.1': @@ -9712,8 +10339,7 @@ snapshots: bare-path: 3.1.1 optional: true - base64-js@1.5.1: - optional: true + base64-js@1.5.1: {} base64id@2.0.0: {} @@ -9769,6 +10395,8 @@ snapshots: body-scroll-lock@4.0.0-beta.0: {} + bowser@2.14.1: {} + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.2 @@ -9785,6 +10413,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.6.0: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -10034,6 +10667,10 @@ snapshots: csstype@3.2.3: {} + csv-parse@5.6.0: {} + + csv-stringify@6.5.2: {} + data-uri-to-buffer@4.0.1: {} data-uri-to-buffer@6.0.2: @@ -10059,7 +10696,8 @@ snapshots: dataloader@2.2.3: {} - date-fns@4.1.0: {} + date-fns@4.1.0: + optional: true date-fns@4.4.0: {} @@ -10136,6 +10774,8 @@ snapshots: destroy@1.2.0: {} + detect-file@1.0.0: {} + detect-libc@2.0.2: {} detect-libc@2.1.2: {} @@ -10553,6 +11193,8 @@ snapshots: - bare-abort-controller optional: true + events@3.3.0: {} + eventsource-parser@3.1.1: optional: true @@ -10564,6 +11206,10 @@ snapshots: expand-template@2.0.3: optional: true + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + expect-type@1.4.0: {} expr-eval-fork@3.0.3: {} @@ -10767,8 +11413,20 @@ snapshots: - supports-color optional: true + find-node-modules@2.1.3: + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + find-root@1.1.0: {} + findup-sync@4.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + focus-trap@7.5.4: dependencies: tabbable: 6.4.0 @@ -10928,6 +11586,20 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -11206,6 +11878,10 @@ snapshots: dependencies: react-is: 16.13.1 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + hono@4.13.13: optional: true @@ -11231,8 +11907,6 @@ snapshots: - supports-color optional: true - http-status@2.1.0: {} - http2-wrapper@2.2.1: dependencies: quick-lru: 5.1.1 @@ -11289,8 +11963,7 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: - optional: true + ini@1.3.8: {} ink-spinner@5.0.0(ink@6.3.0(@types/react@19.2.15)(react@19.2.3))(react@19.2.3): dependencies: @@ -11523,6 +12196,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-windows@1.0.2: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -11533,7 +12208,7 @@ snapshots: jiti@1.21.7: {} - jose@6.2.3: {} + jose@6.2.12: {} jose@6.2.9: {} @@ -11646,6 +12321,8 @@ snapshots: lines-and-columns@1.2.4: {} + lodash.get@4.4.2: {} + lodash.topath@4.5.2: {} lodash@4.18.1: {} @@ -11931,6 +12608,8 @@ snapshots: merge2@1.4.1: {} + merge@2.1.1: {} + methods@1.1.2: {} micromark-core-commonmark@2.0.3: @@ -12601,6 +13280,8 @@ snapshots: unist-util-visit-children: 3.0.0 vfile: 6.0.3 + parse-passwd@1.0.0: {} + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -12626,10 +13307,10 @@ snapshots: path-type@4.0.0: {} - payload@4.0.0-canary.38(graphql@16.11.0)(typescript@7.0.2): + payload@4.0.0-beta.0(graphql@16.11.0)(typescript@7.0.2): dependencies: '@next/env': 16.4.0 - '@payloadcms/translations': 4.0.0-canary.38 + '@payloadcms/translations': 4.0.0-beta.0 '@standard-schema/spec': 1.1.0 '@types/busboy': 1.5.4 bson-objectid: 2.0.4 @@ -12645,10 +13326,9 @@ snapshots: file-type: 22.0.1 get-tsconfig: 4.14.0 graphql: 16.11.0 - http-status: 2.1.0 image-dimensions: 2.5.1 ipaddr.js: 2.3.0 - jose: 6.2.3 + jose: 6.2.12 json-schema-to-typescript: 15.0.3 path-to-regexp: 6.3.0 pino: 10.3.1 @@ -12935,7 +13615,7 @@ snapshots: dependencies: '@floating-ui/react': 0.27.19(react-dom@19.3.0(react@19.3.0))(react@19.3.0) clsx: 2.1.1 - date-fns: 4.1.0 + date-fns: 4.4.0 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -12999,7 +13679,6 @@ snapshots: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - optional: true readdirp@3.6.0: dependencies: @@ -13224,6 +13903,11 @@ snapshots: resolve-alpn@1.2.1: {} + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -13730,6 +14414,11 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + stream-browserify@3.0.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + streamsearch@1.1.0: {} streamx@2.28.0: @@ -13781,7 +14470,6 @@ snapshots: string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - optional: true stringify-entities@4.0.4: dependencies: @@ -13801,6 +14489,11 @@ snapshots: strip-json-comments@5.0.3: {} + stripe@10.17.0: + dependencies: + '@types/node': 26.6.4 + qs: 6.15.3 + strnum@2.4.2: dependencies: anynum: 1.0.1 @@ -14409,6 +15102,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 80b7c5a..ec1a838 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -19,10 +19,20 @@ allowBuilds: sharp: true minimumReleaseAgeExclude: - '@seshuk/payload-plugin-tooling' - - '@payloadcms/db-sqlite@4.0.0-canary.38' - - '@payloadcms/drizzle@4.0.0-canary.38' - - '@payloadcms/graphql@4.0.0-canary.38' - - '@payloadcms/next@4.0.0-canary.38' - - '@payloadcms/translations@4.0.0-canary.38' - - '@payloadcms/ui@4.0.0-canary.38' - - payload@4.0.0-canary.38 + - '@payloadcms/db-sqlite@4.0.0-beta.0' + - '@payloadcms/drizzle@4.0.0-beta.0' + - '@payloadcms/graphql@4.0.0-beta.0' + - '@payloadcms/next@4.0.0-beta.0' + - '@payloadcms/plugin-cloud-storage@4.0.0-beta.0' + - '@payloadcms/plugin-ecommerce@4.0.0-beta.0' + - '@payloadcms/plugin-import-export@4.0.0-beta.0' + - '@payloadcms/plugin-mcp@4.0.0-beta.0' + - '@payloadcms/plugin-multi-tenant@4.0.0-beta.0' + - '@payloadcms/plugin-search@4.0.0-beta.0' + - '@payloadcms/plugin-seo@4.0.0-beta.0' + - '@payloadcms/plugin-stripe@4.0.0-beta.0' + - '@payloadcms/storage-r2@4.0.0-beta.0' + - '@payloadcms/storage-s3@4.0.0-beta.0' + - '@payloadcms/translations@4.0.0-beta.0' + - '@payloadcms/ui@4.0.0-beta.0' + - payload@4.0.0-beta.0 diff --git a/src/server/spec/buildDocument.ts b/src/server/spec/buildDocument.ts index 04b0ee3..4bb9c98 100644 --- a/src/server/spec/buildDocument.ts +++ b/src/server/spec/buildDocument.ts @@ -18,11 +18,12 @@ import type { import { buildAuthPaths } from '@/server/spec/paths/auth.js' import { buildCollectionPaths } from '@/server/spec/paths/collections.js' -import { buildCustomEndpointPaths } from '@/server/spec/paths/custom.js' +import { buildCustomEndpointPaths, getOpenapiMeta } from '@/server/spec/paths/custom.js' import { buildGlobalPaths } from '@/server/spec/paths/globals.js' import { buildJobsPaths } from '@/server/spec/paths/jobs.js' import { buildSystemPaths } from '@/server/spec/paths/system.js' import { buildVersionPaths, versionComponentSchemas } from '@/server/spec/paths/versions.js' +import { OFFICIAL_PLUGINS } from '@/server/spec/plugins/index.js' import { PLUGIN_NAME } from '@/shared/constants.js' import { makeT } from '@/shared/translations/index.js' import type { BuildContext, OperationKind, ResolvedOptions } from '@/shared/types/index.js' @@ -38,7 +39,7 @@ import { securityScheme, } from './components.js' import { buildBlockSchema, buildEntitySchemas } from './entitySchemas.js' -import { filterOperations, shouldIncludeCollection, shouldIncludeGlobal } from './filters.js' +import { filterOperations, shouldIncludeCollection, shouldIncludeGlobal, stripPluginKeys } from './filters.js' import { applyHierarchy } from './hierarchy.js' import { blockSchemaName, @@ -56,6 +57,8 @@ import { buildParamSchemas, buildQueryOperationsSchema, collectionHasFilters } f import { applySecurity, evaluateAccess, resolveEntitySecurity, securedRequirement } from './security.js' import { buildTagHierarchy, type CollectionTagInfo, type GlobalTagInfo, type SystemTag } from './tags.js' +const OPERATION_METHODS = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace', 'query']) + const resolveDescription = (description: unknown, ctx: BuildContext): string | undefined => { if (description == null) return undefined const resolved: unknown = getTranslation(description as string, ctx.i18n) @@ -93,11 +96,17 @@ export const buildDocument = async (input: BuildInput): Promise => { const { filters } = options const login = options.serve && options.interactiveAuth.enabled - const finalize = async (group: PathsObject, kind: OperationKind, slug?: string): Promise => + const finalize = async ( + group: PathsObject, + kind: OperationKind, + slug?: string, + plugin?: string, + ): Promise => applySecurity({ - paths: await filterOperations({ paths: group, slug, kind, filters }), + paths: await filterOperations({ paths: group, slug, kind, filters, plugin }), slug, kind, + plugin, security: options.security, login, }) @@ -230,7 +239,10 @@ export const buildDocument = async (input: BuildInput): Promise => { if (filters.includeCustom) { try { - Object.assign(paths, await finalize(buildCustomEndpointPaths({ config, collections, globals, ctx }), 'custom')) + Object.assign( + paths, + stripPluginKeys(await finalize(buildCustomEndpointPaths({ config, collections, globals, ctx }), 'custom')), + ) } catch (error) { logger.warn(`${PLUGIN_NAME}: failed to collect custom endpoints: ${(error as Error).message}`) } @@ -254,11 +266,42 @@ export const buildDocument = async (input: BuildInput): Promise => { } const uploadSlugs = collections.filter((c) => c.upload).map((c) => c.slug) - Object.assign(paths, await finalize(buildSystemPaths({ ctx, access, uploadSlugs }), 'system')) + const reorderMounted = config.endpoints?.some( + (e) => e.method === 'post' && e.path === '/reorder' && !getOpenapiMeta(e), + ) + const orderableJoinTargets = new Set( + input.collections.flatMap((c) => + Object.entries(c.joins ?? {}) + .filter(([, joins]) => joins.some((j) => j.field.orderable)) + .map(([slug]) => slug), + ), + ) + const reorderSlugs = reorderMounted + ? collections.filter((c) => c.orderable || orderableJoinTargets.has(c.slug)).map((c) => c.slug) + : [] + Object.assign(paths, await finalize(buildSystemPaths({ ctx, access, uploadSlugs, reorderSlugs }), 'system')) + + const pluginTags: string[] = [] + for (const plugin of OFFICIAL_PLUGINS) { + try { + const installed = config.plugins?.find((p) => p.slug === plugin.slug) + if (!(plugin.installed?.(config) ?? installed)) continue + const groups = plugin.build({ config, collections, ctx, options: installed?.options, schemas, t }) + for (const group of groups) { + const finalized = await finalize(group.paths, 'plugin', group.slug, plugin.slug) + if (Object.keys(finalized).length === 0) continue + for (const [path, item] of Object.entries(finalized)) paths[path] = { ...paths[path], ...item } + if (!pluginTags.includes(plugin.tag)) pluginTags.push(plugin.tag) + } + } catch (error) { + logger.warn(`${PLUGIN_NAME}: skipped plugin "${plugin.slug}": ${(error as Error).message}`) + } + } const systemTags: SystemTag[] = [] if (hasJobs) systemTags.push('Jobs') if (uploadSlugs.length > 0) systemTags.push('Uploads') + if (reorderSlugs.length > 0) systemTags.push('Reorder') if (access) systemTags.push('Access') const supportedTimezones = config.admin?.timezones?.supportedTimezones @@ -313,15 +356,26 @@ export const buildDocument = async (input: BuildInput): Promise => { collections: collectionTagInfos, globals: globalTagInfos, systemTags, + pluginTags, t, nested: options.nestedTags, }), 'x-doc-languages': ctx.docLanguages, } as Document + if (options.extensions.some((ext) => ext.transform)) doc = structuredClone(doc) + for (const ext of options.extensions) { if (ext.paths) { - doc.paths = deepMerge(doc.paths ?? {}, ext.paths) as PathsObject + const base: PathsObject = { ...doc.paths } + for (const [path, item] of Object.entries(ext.paths)) { + const current = base[path] + if (!current || !item) continue + base[path] = Object.fromEntries( + Object.entries(current).filter(([key]) => !(OPERATION_METHODS.has(key) && key in item)), + ) + } + doc.paths = deepMerge(base, ext.paths) as PathsObject } if (ext.components) { doc.components = deepMerge( diff --git a/src/server/spec/filters.ts b/src/server/spec/filters.ts index b3ee782..7efac63 100644 --- a/src/server/spec/filters.ts +++ b/src/server/spec/filters.ts @@ -75,18 +75,34 @@ const ruleMatchesMethod = (rule: OperationRule, method: HttpMethod): boolean => return Array.isArray(rule.method) ? rule.method.includes(method) : rule.method === method } -const ruleMatchesSlug = (rule: OperationRule, slug: string | undefined): boolean => { - if (rule.slug === undefined) return true - if (slug === undefined) return false - return rule.slug instanceof RegExp ? rule.slug.test(slug) : rule.slug === slug +const matchesOptional = (matcher: string | RegExp | undefined, value: string | undefined): boolean => { + if (matcher === undefined) return true + if (value === undefined) return false + return matcher instanceof RegExp ? matcher.test(value) : matcher === value } -const ruleMatches = (rule: OperationRule, { method, slug, kind, path }: OperationContext): boolean => +const ruleMatches = (rule: OperationRule, { method, slug, kind, path, plugin }: OperationContext): boolean => ruleMatchesMethod(rule, method) && - ruleMatchesSlug(rule, slug) && + matchesOptional(rule.slug, slug) && + matchesOptional(rule.plugin, plugin) && (rule.kind === undefined || rule.kind === kind) && (rule.path === undefined || rule.path.test(path)) +export const PLUGIN_KEY = 'x-payload-plugin' + +export const operationPlugin = (operation: unknown, plugin: string | undefined): string | undefined => { + const marked = (operation as Record | undefined)?.[PLUGIN_KEY] + return plugin ?? (typeof marked === 'string' ? marked : undefined) +} + +export const stripPluginKeys = (paths: PathsObject): PathsObject => { + for (const item of Object.values(paths)) { + for (const method of HTTP_METHODS) + delete (item as Record | undefined>)[method]?.[PLUGIN_KEY] + } + return paths +} + const isOperationExcluded = async (filters: ResolvedFilters, op: OperationContext): Promise => { for (const rule of filters.excludeOperations) { if (typeof rule === 'function' ? (await rule(op)) === true : ruleMatches(rule, op)) return true @@ -98,11 +114,13 @@ export const filterOperations = async ({ paths, slug, kind, + plugin, filters, }: { paths: PathsObject slug?: string kind: OperationKind + plugin?: string filters: ResolvedFilters }): Promise => { if (filters.excludeOperations.length === 0) return paths @@ -113,7 +131,8 @@ export const filterOperations = async ({ const ops = item as Record for (const method of HTTP_METHODS) { if (!ops[method]) continue - if (await isOperationExcluded(filters, { method, path, slug, kind })) { + const op = { method, path, slug, kind, plugin: operationPlugin(ops[method], plugin) } + if (await isOperationExcluded(filters, op)) { delete ops[method] } else { remaining += 1 diff --git a/src/server/spec/paths/collections.ts b/src/server/spec/paths/collections.ts index 48b1fbd..9e4d738 100644 --- a/src/server/spec/paths/collections.ts +++ b/src/server/spec/paths/collections.ts @@ -1,4 +1,5 @@ import type { + ParameterObject, PathsObject, ReferenceObject, RequestBodyObject, @@ -11,6 +12,7 @@ import { ERRORS, docAccessOperation, errorResponses, + jsonBody, jsonOk, jsonResponse, uploadRequestBody, @@ -26,7 +28,7 @@ import { writeParams, type WriteOperation, } from '@/server/spec/params.js' -import type { EntitySecurity } from '@/server/spec/security.js' +import { type EntitySecurity, securedRequirement } from '@/server/spec/security.js' import { makeT } from '@/shared/translations/index.js' import type { BuildContext } from '@/shared/types/index.js' @@ -263,8 +265,12 @@ export const buildCollectionPaths = ({ if (isUpload) { const file = { content: { '*/*': { schema: { type: 'string', format: 'binary' } } } } as const + const fileParams: ParameterObject[] = [{ name: 'filename', in: 'path', required: true, schema: { type: 'string' } }] + if (collection.versions) { + fileParams.push({ name: 'version', in: 'query', description: t('paramFileVersion'), schema: { type: idType } }) + } paths[`${base}/file/{filename}`] = { - parameters: [{ name: 'filename', in: 'path', required: true, schema: { type: 'string' } }], + parameters: fileParams, get: { tags: [name], operationId: `get${name}File`, @@ -276,6 +282,85 @@ export const buildCollectionPaths = ({ security: secRead, }, } + paths[`${base}/{id}/rename`] = { + parameters: [{ name: 'id', in: 'path', required: true, schema: { type: idType } }], + post: { + tags: [name], + operationId: `rename${name}File`, + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { filename: { type: 'string' }, draft: { type: 'boolean' } }, + required: ['filename'], + }, + }, + }, + }, + responses: updateResponse, + security: secUpdate, + }, + } + } + + if (collection.slug === 'payload-preferences') { + delete paths[`${base}/count`] + delete paths[`${base}/validate`] + delete paths[`${base}/access`] + const keyParam: ParameterObject[] = [ + { name: 'id', in: 'path', required: true, description: t('paramPreferenceKey'), schema: { type: 'string' } }, + ] + const secured = securedRequirement() + paths[`${base}/{id}`] = { + ...paths[`${base}/{id}`], + get: { + tags: [name], + operationId: `find${name}ByKey`, + summary: t('preferenceGet'), + parameters: keyParam, + responses: { + ...jsonOk(t('preferenceGetResult'), { + anyOf: [ + docRef, + { + type: 'object', + properties: { message: { type: 'string' }, value: { const: null } }, + required: ['message', 'value'], + }, + ], + }), + ...errorResponses(['500'], t), + }, + security: secured, + }, + post: { + tags: [name], + operationId: `upsert${name}ByKey`, + summary: t('preferenceSet'), + parameters: keyParam, + requestBody: { + ...jsonBody({ type: 'object', properties: { value: {} }, required: ['value'] }), + description: t('preferenceSetBody'), + }, + responses: { ...jsonOk(t('preferenceSetResult'), mutationSchema), ...errorResponses(['401', '500'], t) }, + security: secured, + }, + delete: { + tags: [name], + operationId: `delete${name}ByKey`, + summary: t('preferenceDelete'), + parameters: keyParam, + responses: { + ...jsonOk(t('preferenceDeleteResult'), { + allOf: [docRef, { type: 'object', properties: { message: { type: 'string' } } }], + }), + ...errorResponses(['401', '404', '500'], t), + }, + security: secured, + }, + } } return paths diff --git a/src/server/spec/paths/custom.ts b/src/server/spec/paths/custom.ts index 2bee942..4b20a4a 100644 --- a/src/server/spec/paths/custom.ts +++ b/src/server/spec/paths/custom.ts @@ -3,12 +3,12 @@ import type { Endpoint, SanitizedCollectionConfig, SanitizedConfig, SanitizedGlo import type { BuildContext } from '@/shared/types/index.js' -const getOpenapiMeta = (endpoint: Endpoint): OperationObject | undefined => { +export const getOpenapiMeta = (endpoint: Endpoint): OperationObject | undefined => { const meta = endpoint.custom?.openapi return meta && typeof meta === 'object' ? (meta as OperationObject) : undefined } -const normalizePath = (prefix: string, path: string): string => { +export const normalizePath = (prefix: string, path: string): string => { const joined = `${prefix}${path.startsWith('/') ? path : `/${path}`}` return joined.replace(/:([A-Za-z0-9_]+)/g, '{$1}') } diff --git a/src/server/spec/paths/system.ts b/src/server/spec/paths/system.ts index 8364549..437cfe6 100644 --- a/src/server/spec/paths/system.ts +++ b/src/server/spec/paths/system.ts @@ -8,10 +8,12 @@ export const buildSystemPaths = ({ ctx, access, uploadSlugs, + reorderSlugs = [], }: { ctx: BuildContext access: boolean uploadSlugs: string[] + reorderSlugs?: string[] }): PathsObject => { const t = makeT(ctx.i18n) const paths: PathsObject = {} @@ -109,5 +111,52 @@ export const buildSystemPaths = ({ } } + if (reorderSlugs.length > 0) { + const idType = ctx.defaultIDType === 'number' ? 'integer' : 'string' + paths[`${ctx.apiRoute}/reorder`] = { + post: { + tags: ['Reorder'], + operationId: 'reorder', + summary: t('reorder'), + requestBody: { + ...jsonBody({ + type: 'object', + properties: { + collectionSlug: { type: 'string', enum: reorderSlugs }, + docsToMove: { type: 'array', minItems: 1, items: { type: idType } }, + newKeyWillBe: { type: 'string', enum: ['greater', 'less'] }, + orderableFieldName: { type: 'string' }, + target: { + type: 'object', + properties: { id: { type: idType }, key: { type: 'string' } }, + required: ['id'], + }, + }, + required: ['collectionSlug', 'docsToMove', 'newKeyWillBe', 'orderableFieldName', 'target'], + }), + description: t('reorderBody'), + }, + responses: { + '200': jsonResponse(t('reorderResult'), { + type: 'object', + properties: { + success: { type: 'boolean' }, + orderValues: { type: 'array', items: { type: 'string' } }, + message: { type: 'string' }, + }, + required: ['success'], + }), + '400': jsonResponse(t('errorReorder400'), { + type: 'object', + properties: { error: { type: 'string' } }, + required: ['error'], + }), + ...errorResponses(['403', '500'], t), + }, + security: [{ [SECURITY_SCHEME_NAME]: [] }], + }, + } + } + return paths } diff --git a/src/server/spec/plugins/ecommerce.ts b/src/server/spec/plugins/ecommerce.ts new file mode 100644 index 0000000..8944108 --- /dev/null +++ b/src/server/spec/plugins/ecommerce.ts @@ -0,0 +1,205 @@ +import type { OperationObject, ParameterObject, SchemaObject } from '@scalar/openapi-types/3.2' + +import { ERROR_SCHEMA_NAME, errorResponses, jsonBody, jsonResponse } from '@/server/spec/components.js' +import { refTo, schemaName } from '@/server/spec/names.js' +import { securedRequirement } from '@/server/spec/security.js' +import type { MessageKey } from '@/shared/translations/types.js' +import { upperFirst } from '@/shared/utils.js' + +import { type OfficialPlugin, collectionGroups, idSchema, messageErrors, mountEndpoints } from './shared.js' +import { stripeWebhookOperation } from './stripe.js' + +const TAG = 'Ecommerce' + +const PAYMENT_PATH = /^\/payments\/([^/]+)\/(initiate|confirm-order)$/ + +const CART_OPERATIONS: Record = { + '/:id/add-item': { operationId: 'addItem', summary: 'ecommerceAddItem' }, + '/:id/remove-item': { operationId: 'removeItem', summary: 'ecommerceRemoveItem' }, + '/:id/update-item': { operationId: 'updateItem', summary: 'ecommerceUpdateItem' }, + '/:id/clear': { operationId: 'clear', summary: 'ecommerceClearCart' }, + '/:id/merge': { operationId: 'merge', summary: 'ecommerceMergeCart' }, +} + +export const ecommerce: OfficialPlugin = { + slug: '@payloadcms/plugin-ecommerce', + tag: TAG, + build: ({ config, collections, ctx, schemas, t }) => { + const id = idSchema(ctx) + const secret: SchemaObject = { type: 'string' } + const failure: SchemaObject = { + type: 'object', + properties: { message: { type: 'string' }, success: { type: 'boolean', enum: [false] } }, + required: ['message', 'success'], + } + + const cartBodies: Record = { + '/:id/add-item': { + type: 'object', + properties: { + item: { + type: 'object', + additionalProperties: true, + properties: { product: id, variant: id }, + required: ['product'], + }, + quantity: { type: 'number', default: 1 }, + secret, + }, + required: ['item'], + }, + '/:id/remove-item': { + type: 'object', + properties: { itemID: { type: 'string' }, secret }, + required: ['itemID'], + }, + '/:id/update-item': { + type: 'object', + properties: { + itemID: { type: 'string' }, + quantity: { + description: t('ecommerceQuantity'), + oneOf: [ + { type: 'number' }, + { type: 'object', properties: { $inc: { type: 'number' } }, required: ['$inc'] }, + ], + }, + removeOnZero: { type: 'boolean', default: true }, + secret, + }, + required: ['itemID', 'quantity'], + }, + '/:id/clear': { type: 'object', properties: { secret } }, + '/:id/merge': { + type: 'object', + properties: { sourceCartID: id, sourceSecret: { type: 'string' } }, + required: ['sourceCartID', 'sourceSecret'], + }, + } + + const cartGroups = collectionGroups( + collections.filter((c) => + (c.endpoints || []).some((endpoint) => endpoint.method === 'post' && endpoint.path === '/:id/add-item'), + ), + ctx, + ({ method, path }, collection): OperationObject | undefined => { + const meta = CART_OPERATIONS[path] + if (!meta || method !== 'post') return undefined + const name = schemaName(collection.slug) + const merge = path === '/:id/merge' + const cart: SchemaObject = schemas[name] + ? { $ref: refTo(name) } + : { type: 'object', additionalProperties: true } + const param: ParameterObject = { name: 'id', in: 'path', required: true, schema: id } + return { + tags: [TAG], + operationId: `${meta.operationId}${name}`, + summary: t(meta.summary), + description: merge ? undefined : t('ecommerceCartAccess'), + parameters: [param], + requestBody: { ...jsonBody(cartBodies[path]!), required: path !== '/:id/clear' }, + responses: { + '200': jsonResponse(t('ecommerceCartResult'), { + type: 'object', + properties: { cart, message: { type: 'string' }, success: { type: 'boolean', enum: [true] } }, + required: ['cart', 'message', 'success'], + }), + ...messageErrors(merge ? ['400', '401'] : ['400'], t, failure), + ...errorResponses(['403'], t), + '404': jsonResponse(t('errorPluginEcommerce404'), { + oneOf: [ + { + type: 'object', + properties: { cart: { type: ['object', 'null'] }, ...failure.properties }, + required: ['cart', 'message', 'success'], + }, + { $ref: refTo(ERROR_SCHEMA_NAME) }, + ], + }), + }, + ...(merge ? { security: securedRequirement() } : {}), + } + }, + ) + + const address: SchemaObject = { type: 'object', additionalProperties: true } + const paymentBody = (extra: Record) => ({ + ...jsonBody({ + type: 'object', + additionalProperties: true, + properties: { + cartID: id, + secret, + customerEmail: { type: 'string', format: 'email' }, + billingAddress: address, + shippingAddress: address, + ...extra, + }, + ...(Object.keys(extra).length > 0 ? { required: Object.keys(extra) } : {}), + }), + description: t('ecommercePaymentBody'), + }) + + const usedIds = new Set() + const uniqueId = (base: string) => { + let operationId = base + for (let n = 2; usedIds.has(operationId); n++) operationId = `${base}${n}` + usedIds.add(operationId) + return operationId + } + + const payments = mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => { + if (method !== 'post') return undefined + if (path === '/payments/stripe/webhooks') { + return stripeWebhookOperation({ tag: TAG, operationId: 'ecommerceStripeWebhooks', t }) + } + const match = PAYMENT_PATH.exec(path) + if (!match) return undefined + const [, methodName = '', step] = match + const stripe = methodName === 'stripe' + const suffix = upperFirst(methodName.replace(/[^A-Za-z0-9]+(.)?/g, (_, c: string = '') => c.toUpperCase())) + if (step === 'initiate') { + return { + tags: [TAG], + operationId: uniqueId(`initiatePayment${suffix}`), + summary: t('ecommerceInitiatePayment', { method: methodName }), + requestBody: paymentBody({}), + responses: { + '200': jsonResponse(t('ecommerceInitiateResult'), { + type: 'object', + additionalProperties: true, + properties: { + message: { type: 'string' }, + ...(stripe ? { clientSecret: { type: 'string' }, paymentIntentID: { type: 'string' } } : {}), + }, + required: ['message'], + }), + ...messageErrors(['400', '404', '500'], t), + }, + } + } + return { + tags: [TAG], + operationId: uniqueId(`confirmOrder${suffix}`), + summary: t('ecommerceConfirmOrder', { method: methodName }), + requestBody: paymentBody(stripe ? { paymentIntentID: { type: 'string' } } : {}), + responses: { + '200': jsonResponse(t('ecommerceConfirmResult'), { + type: 'object', + additionalProperties: true, + properties: { + message: { type: 'string' }, + orderID: id, + transactionID: id, + ...(stripe ? { accessToken: { type: 'string' } } : {}), + }, + required: ['message', 'orderID', 'transactionID'], + }), + ...messageErrors(['400', '404', '500'], t), + }, + } + }) + + return [{ paths: payments }, ...cartGroups] + }, +} diff --git a/src/server/spec/plugins/importExport.ts b/src/server/spec/plugins/importExport.ts new file mode 100644 index 0000000..cee3c6a --- /dev/null +++ b/src/server/spec/plugins/importExport.ts @@ -0,0 +1,152 @@ +import type { OperationObject, SchemaObject } from '@scalar/openapi-types/3.2' +import type { SanitizedCollectionConfig } from 'payload' + +import { errorResponses, jsonBody, jsonResponse } from '@/server/spec/components.js' +import { schemaName } from '@/server/spec/names.js' +import { securedRequirement } from '@/server/spec/security.js' + +import { type OfficialPlugin, collectionGroups, messageErrors } from './shared.js' + +const TAG = 'Import/Export' + +const targetSlugs = (collection: SanitizedCollectionConfig): string[] | undefined => { + const meta = (collection.admin?.custom as Record | undefined)?.[ + 'plugin-import-export' + ] + return Array.isArray(meta?.collectionSlugs) ? (meta.collectionSlugs as string[]) : undefined +} + +const errorSchema: SchemaObject = { type: 'object', properties: { error: { type: 'string' } }, required: ['error'] } + +const pageProperties: Record = { + docs: { type: 'array', items: { type: 'object', additionalProperties: true } }, + hasNextPage: { type: 'boolean' }, + hasPrevPage: { type: 'boolean' }, + limit: { type: 'integer' }, + maxLimit: { type: 'integer' }, + page: { type: 'integer' }, + totalDocs: { type: 'integer' }, + totalPages: { type: 'integer' }, +} + +const previewPaging: Record = { + previewLimit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + previewPage: { type: 'integer', minimum: 1, default: 1 }, +} + +const format: SchemaObject = { type: 'string', enum: ['csv', 'json'] } +const drafts: SchemaObject = { type: 'string', enum: ['yes', 'no'] } + +const query: Record = { + fields: { type: 'array', items: { type: 'string' } }, + limit: { type: 'integer', minimum: 0 }, + locale: { type: 'string' }, + sort: { type: 'string' }, + where: { type: 'object', additionalProperties: true }, +} + +export const importExport: OfficialPlugin = { + slug: '@payloadcms/plugin-import-export', + tag: TAG, + build: ({ collections, ctx, t }) => { + const visible = new Set(collections.map((c) => c.slug)) + return collectionGroups(collections, ctx, ({ method, path }, collection): OperationObject | undefined => { + const slugs = targetSlugs(collection)?.filter((slug) => visible.has(slug)) + if (!slugs || method !== 'post') return undefined + const name = schemaName(collection.slug) + const collectionSlug: SchemaObject = { type: 'string', ...(slugs.length > 0 ? { enum: slugs } : {}) } + if (path === '/download') { + return { + tags: [TAG], + operationId: `download${name}`, + summary: t('exportDownload'), + requestBody: jsonBody({ + type: 'object', + properties: { + data: { + type: 'object', + additionalProperties: true, + properties: { + collectionSlug, + format, + name: { type: 'string' }, + ...query, + page: { type: 'integer' }, + drafts, + }, + required: ['collectionSlug', 'format'], + }, + }, + required: ['data'], + }), + responses: { + '200': { + description: t('exportDownloadResult'), + headers: { 'Content-Disposition': { schema: { type: 'string' } } }, + content: { + 'text/csv': { schema: { type: 'string' } }, + 'application/json': { + schema: { type: 'array', items: { type: 'object', additionalProperties: true } }, + }, + }, + }, + ...errorResponses(['400'], t), + }, + security: securedRequirement(), + } + } + if (path === '/export-preview') { + return { + tags: [TAG], + operationId: `preview${name}`, + summary: t('exportPreview'), + requestBody: jsonBody({ + type: 'object', + additionalProperties: true, + properties: { collectionSlug, format, ...query, ...previewPaging, draft: drafts }, + required: ['collectionSlug'], + }), + responses: { + '200': jsonResponse(t('previewResult'), { + type: 'object', + properties: { + columns: { type: 'array', items: { type: 'string' } }, + exportTotalDocs: { type: 'integer' }, + ...pageProperties, + }, + required: ['docs', 'page', 'limit', 'totalDocs', 'totalPages'], + }), + ...messageErrors(['400'], t, errorSchema), + }, + } + } + if (path === '/preview-data') { + return { + tags: [TAG], + operationId: `preview${name}Data`, + summary: t('importPreview'), + requestBody: jsonBody({ + type: 'object', + additionalProperties: true, + properties: { + collectionSlug, + fileData: { type: 'string', contentEncoding: 'base64', description: t('importFileData') }, + format, + ...previewPaging, + }, + required: ['collectionSlug', 'fileData'], + }), + responses: { + '200': jsonResponse(t('previewResult'), { + type: 'object', + properties: { ...pageProperties, limitExceeded: { type: 'boolean' } }, + required: ['docs', 'page', 'limit', 'totalDocs', 'totalPages', 'limitExceeded'], + }), + ...messageErrors(['400', '500'], t, errorSchema), + }, + } + } + return undefined + }) + }, +} diff --git a/src/server/spec/plugins/index.ts b/src/server/spec/plugins/index.ts new file mode 100644 index 0000000..b94e1b1 --- /dev/null +++ b/src/server/spec/plugins/index.ts @@ -0,0 +1,20 @@ +import { ecommerce } from './ecommerce.js' +import { importExport } from './importExport.js' +import { mcp } from './mcp.js' +import { multiTenant } from './multiTenant.js' +import { search } from './search.js' +import { seo } from './seo.js' +import type { OfficialPlugin } from './shared.js' +import { storageR2 } from './storageR2.js' +import { stripe } from './stripe.js' + +export const OFFICIAL_PLUGINS: OfficialPlugin[] = [ + ecommerce, + stripe, + mcp, + seo, + search, + multiTenant, + importExport, + storageR2, +] diff --git a/src/server/spec/plugins/mcp.ts b/src/server/spec/plugins/mcp.ts new file mode 100644 index 0000000..c711c32 --- /dev/null +++ b/src/server/spec/plugins/mcp.ts @@ -0,0 +1,100 @@ +import type { OperationObject, SchemaObject } from '@scalar/openapi-types/3.2' + +import { ERROR_SCHEMA_NAME, errorResponses, jsonBody, jsonResponse } from '@/server/spec/components.js' +import { refTo } from '@/server/spec/names.js' + +import { type OfficialPlugin, mountEndpoints } from './shared.js' + +const TAG = 'MCP' + +const jsonRpcId: SchemaObject = { type: ['string', 'integer', 'null'] } + +const jsonRpc: SchemaObject = { + type: 'object', + additionalProperties: true, + properties: { + jsonrpc: { type: 'string', enum: ['2.0'] }, + id: jsonRpcId, + method: { type: 'string' }, + params: { type: 'object', additionalProperties: true }, + result: {}, + error: { type: 'object', additionalProperties: true }, + }, + required: ['jsonrpc'], +} + +const jsonRpcBatch: SchemaObject = { oneOf: [jsonRpc, { type: 'array', items: jsonRpc }] } + +const jsonRpcError: SchemaObject = { + type: 'object', + properties: { + jsonrpc: { type: 'string', enum: ['2.0'] }, + error: { + type: 'object', + properties: { code: { type: 'integer' }, message: { type: 'string' }, data: {} }, + required: ['code', 'message'], + }, + id: jsonRpcId, + }, + required: ['jsonrpc', 'error', 'id'], +} + +export const mcp: OfficialPlugin = { + slug: '@payloadcms/plugin-mcp', + tag: TAG, + build: ({ config, ctx, t }) => { + const rpcError = (description: string) => jsonResponse(description, jsonRpcError) + const post: OperationObject = { + tags: [TAG], + operationId: 'mcp', + summary: t('mcp'), + description: t('mcpDesc'), + parameters: [ + { + name: 'overrideAccess', + in: 'query', + description: t('mcpOverrideAccess'), + schema: { type: 'string', enum: ['true', 'false'] }, + }, + { + name: 'MCP-Protocol-Version', + in: 'header', + description: t('mcpProtocolVersion'), + schema: { type: 'string' }, + }, + ], + requestBody: jsonBody(jsonRpcBatch), + responses: { + '200': jsonResponse(t('mcpResult'), jsonRpcBatch), + '202': { description: t('mcpResult202') }, + '400': jsonResponse(t('errorPlugin400'), { oneOf: [jsonRpcError, { $ref: refTo(ERROR_SCHEMA_NAME) }] }), + ...errorResponses(['401'], t), + '404': rpcError(t('errorPluginMcp404')), + '406': rpcError(t('errorPluginMcp406')), + '413': rpcError(t('errorPluginMcp413')), + '415': rpcError(t('errorPluginMcp415')), + '500': rpcError(t('errorPlugin500')), + }, + } + const get: OperationObject = { + tags: [TAG], + operationId: 'mcpStream', + summary: t('mcpGet'), + responses: { + '405': { + description: t('errorPluginMcpGet405'), + headers: { Allow: { schema: { type: 'string', enum: ['POST'] } } }, + }, + }, + } + return [ + { + paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => { + if (path !== '/mcp') return undefined + if (method === 'post') return post + return method === 'get' ? get : undefined + }), + }, + ] + }, +} diff --git a/src/server/spec/plugins/multiTenant.ts b/src/server/spec/plugins/multiTenant.ts new file mode 100644 index 0000000..7d02ce7 --- /dev/null +++ b/src/server/spec/plugins/multiTenant.ts @@ -0,0 +1,48 @@ +import { errorResponses, jsonResponse } from '@/server/spec/components.js' +import { schemaName } from '@/server/spec/names.js' +import { securedRequirement } from '@/server/spec/security.js' + +import { type OfficialPlugin, collectionGroups } from './shared.js' + +const TAG = 'Multi-tenant' + +export const multiTenant: OfficialPlugin = { + slug: '@payloadcms/plugin-multi-tenant', + tag: TAG, + build: ({ collections, ctx, options, t }) => { + const tenantsSlug = (options?.tenantsSlug as string | undefined) || 'tenants' + return collectionGroups( + collections.filter((c) => c.slug === tenantsSlug), + ctx, + ({ method, path }, collection) => + method === 'get' && path === '/populate-tenant-options' + ? { + tags: [TAG], + operationId: `populate${schemaName(collection.slug)}Options`, + summary: t('tenantOptions'), + responses: { + '200': jsonResponse(t('tenantOptionsResult'), { + type: 'object', + properties: { + tenantOptions: { + type: 'array', + items: { + type: 'object', + properties: { + label: { type: 'string' }, + value: { oneOf: [{ type: 'string' }, { type: 'number' }] }, + }, + required: ['label', 'value'], + }, + }, + }, + required: ['tenantOptions'], + }), + ...errorResponses(['401'], t), + }, + security: securedRequirement(), + } + : undefined, + ) + }, +} diff --git a/src/server/spec/plugins/search.ts b/src/server/spec/plugins/search.ts new file mode 100644 index 0000000..d0d9a3b --- /dev/null +++ b/src/server/spec/plugins/search.ts @@ -0,0 +1,48 @@ +import { jsonBody, jsonResponse } from '@/server/spec/components.js' +import { schemaName } from '@/server/spec/names.js' +import { securedRequirement } from '@/server/spec/security.js' + +import { type OfficialPlugin, collectionGroups, messageErrors, messageSchema } from './shared.js' + +const TAG = 'Search' + +export const search: OfficialPlugin = { + slug: '@payloadcms/plugin-search', + tag: TAG, + build: ({ collections, ctx, options, t }) => { + const overrides = options?.searchOverrides as { slug?: string } | undefined + const searchSlug = overrides?.slug || 'search' + const visible = new Set(collections.map((c) => c.slug)) + const indexed = Array.isArray(options?.collections) + ? (options.collections as string[]).filter((slug) => visible.has(slug)) + : [] + return collectionGroups( + collections.filter((c) => c.slug === searchSlug), + ctx, + ({ method, path }, collection) => + method === 'post' && path === '/reindex' + ? { + tags: [TAG], + operationId: `reindex${schemaName(collection.slug)}`, + summary: t('searchReindex'), + requestBody: jsonBody({ + type: 'object', + properties: { + collections: { + type: 'array', + minItems: 1, + items: { type: 'string', ...(indexed.length > 0 ? { enum: indexed } : {}) }, + }, + }, + required: ['collections'], + }), + responses: { + '200': jsonResponse(t('searchReindexResult'), messageSchema), + ...messageErrors(['400', '401', '500'], t), + }, + security: securedRequirement(), + } + : undefined, + ) + }, +} diff --git a/src/server/spec/plugins/seo.ts b/src/server/spec/plugins/seo.ts new file mode 100644 index 0000000..57148ce --- /dev/null +++ b/src/server/spec/plugins/seo.ts @@ -0,0 +1,67 @@ +import type { OperationObject, SchemaObject } from '@scalar/openapi-types/3.2' + +import { errorResponses, jsonBody, jsonResponse } from '@/server/spec/components.js' +import { securedRequirement } from '@/server/spec/security.js' +import type { MessageKey } from '@/shared/translations/types.js' + +import { type OfficialPlugin, idSchema, mountEndpoints } from './shared.js' + +const TAG = 'SEO' + +const GENERATORS: Record = { + '/plugin-seo/generate-title': { operationId: 'seoGenerateTitle', summary: 'seoTitle' }, + '/plugin-seo/generate-description': { operationId: 'seoGenerateDescription', summary: 'seoDescription' }, + '/plugin-seo/generate-url': { operationId: 'seoGenerateUrl', summary: 'seoUrl' }, + '/plugin-seo/generate-image': { operationId: 'seoGenerateImage', summary: 'seoImage', image: true }, +} + +export const seo: OfficialPlugin = { + slug: '@payloadcms/plugin-seo', + tag: TAG, + build: ({ config, ctx, t }) => { + const id = idSchema(ctx) + const operation = (path: string): OperationObject | undefined => { + const generator = GENERATORS[path] + if (!generator) return undefined + const result: SchemaObject = generator.image + ? { oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'object', additionalProperties: true }] } + : { type: 'string' } + return { + tags: [TAG], + operationId: generator.operationId, + summary: t(generator.summary), + requestBody: { + ...jsonBody({ + type: 'object', + additionalProperties: true, + properties: { + id: { oneOf: [{ type: 'string' }, { type: 'number' }] }, + collectionSlug: { type: 'string' }, + globalSlug: { type: 'string' }, + doc: { type: 'object', additionalProperties: true, properties: { id } }, + locale: { type: 'string' }, + title: { type: 'string' }, + }, + }), + description: t('seoBody'), + }, + responses: { + '200': jsonResponse(t('seoResult'), { + type: 'object', + properties: { result }, + required: ['result'], + }), + ...errorResponses(['401', '403', '500'], t), + }, + security: securedRequirement(), + } + } + return [ + { + paths: mountEndpoints(config.endpoints, ctx.apiRoute, (endpoint) => + endpoint.method === 'post' ? operation(endpoint.path) : undefined, + ), + }, + ] + }, +} diff --git a/src/server/spec/plugins/shared.ts b/src/server/spec/plugins/shared.ts new file mode 100644 index 0000000..e02cf35 --- /dev/null +++ b/src/server/spec/plugins/shared.ts @@ -0,0 +1,82 @@ +import type { OperationObject, PathsObject, ResponsesObject, SchemaObject } from '@scalar/openapi-types/3.2' +import type { Endpoint, SanitizedCollectionConfig, SanitizedConfig } from 'payload' + +import { jsonResponse } from '@/server/spec/components.js' +import { getOpenapiMeta, normalizePath } from '@/server/spec/paths/custom.js' +import type { Translate } from '@/shared/translations/types.js' +import type { BuildContext } from '@/shared/types/index.js' + +export interface PluginBuildArgs { + config: SanitizedConfig + collections: SanitizedCollectionConfig[] + ctx: BuildContext + options: Record | undefined + schemas: Record + t: Translate +} + +export interface PluginGroup { + paths: PathsObject + slug?: string +} + +export interface OfficialPlugin { + slug: string + tag: string + installed?: (config: SanitizedConfig) => boolean + build: (args: PluginBuildArgs) => PluginGroup[] +} + +export const mountEndpoints = ( + endpoints: Endpoint[] | false | undefined, + prefix: string, + operation: (endpoint: Endpoint) => OperationObject | undefined, +): PathsObject => { + const out: PathsObject = {} + for (const endpoint of endpoints || []) { + if (getOpenapiMeta(endpoint)) continue + const op = operation(endpoint) + if (!op) continue + const path = normalizePath(prefix, endpoint.path) + out[path] = { ...out[path], [endpoint.method]: op } + } + return out +} + +export const collectionGroups = ( + collections: SanitizedCollectionConfig[], + ctx: BuildContext, + operation: (endpoint: Endpoint, collection: SanitizedCollectionConfig) => OperationObject | undefined, +): PluginGroup[] => + collections + .map((collection) => ({ + slug: collection.slug, + paths: mountEndpoints(collection.endpoints, `${ctx.apiRoute}/${collection.slug}`, (endpoint) => + operation(endpoint, collection), + ), + })) + .filter((group) => Object.keys(group.paths).length > 0) + +export const idSchema = (ctx: BuildContext): SchemaObject => ({ + type: ctx.defaultIDType === 'number' ? 'integer' : 'string', +}) + +export const messageSchema: SchemaObject = { + type: 'object', + properties: { message: { type: 'string' } }, + required: ['message'], +} + +const MESSAGE_ERROR_KEYS = { + '400': 'errorPlugin400', + '401': 'errorPlugin401', + '403': 'errorPlugin403', + '404': 'errorPlugin404', + '500': 'errorPlugin500', +} as const + +export const messageErrors = ( + codes: Array, + t: Translate, + schema: SchemaObject = messageSchema, +): ResponsesObject => Object.fromEntries(codes.map((code) => [code, jsonResponse(t(MESSAGE_ERROR_KEYS[code]), schema)])) diff --git a/src/server/spec/plugins/storageR2.ts b/src/server/spec/plugins/storageR2.ts new file mode 100644 index 0000000..be7e2a8 --- /dev/null +++ b/src/server/spec/plugins/storageR2.ts @@ -0,0 +1,89 @@ +import type { OperationObject, ParameterObject } from '@scalar/openapi-types/3.2' + +import { errorResponses } from '@/server/spec/components.js' +import { securedRequirement } from '@/server/spec/security.js' + +import { type OfficialPlugin, mountEndpoints } from './shared.js' + +const TAG = 'Storage R2' + +const UPLOAD_PATH = /^\/storage-r2-multi-part-upload(?:-(\d+))?$/ + +const query = (name: string, required: boolean, type: 'string' | 'integer' = 'string'): ParameterObject => ({ + name, + in: 'query', + required, + schema: { type }, +}) + +export const storageR2: OfficialPlugin = { + slug: '@payloadcms/storage-r2', + tag: TAG, + installed: (config) => (config.storage ?? []).some((adapter) => adapter?.name === 'r2'), + build: ({ config, ctx, t }) => { + const upload = (suffix: string): OperationObject => ({ + tags: [TAG], + operationId: `r2MultipartUpload${suffix}`, + summary: t('r2Upload'), + description: t('r2UploadDesc'), + parameters: [ + query('collection', true), + query('fileName', true), + query('fileType', true), + query('docPrefix', false), + query('multipartId', false), + query('multipartKey', false), + query('multipartNumber', false, 'integer'), + query('signedReceipt', false), + ], + requestBody: { + content: { + 'application/octet-stream': { schema: { type: 'string', format: 'binary' } }, + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { partNumber: { type: 'integer' }, etag: { type: 'string' } }, + required: ['partNumber', 'etag'], + }, + }, + }, + }, + }, + responses: { + '200': { + description: t('r2UploadResult'), + content: { + 'application/json': { + schema: { + type: 'object', + additionalProperties: true, + properties: { + filename: { type: 'string' }, + key: { type: 'string' }, + uploadId: { type: 'string' }, + uploadReference: { type: 'object', additionalProperties: true }, + partNumber: { type: 'integer' }, + etag: { type: 'string' }, + }, + }, + }, + 'text/plain': { schema: { type: 'string' } }, + }, + }, + ...errorResponses(['400', '403', '500'], t), + '412': { description: t('errorStorageR2412'), content: { 'text/plain': { schema: { type: 'string' } } } }, + }, + security: securedRequirement(), + }) + return [ + { + paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => { + const match = UPLOAD_PATH.exec(path) + return method === 'post' && match ? upload(match[1] ?? '') : undefined + }), + }, + ] + }, +} diff --git a/src/server/spec/plugins/stripe.ts b/src/server/spec/plugins/stripe.ts new file mode 100644 index 0000000..5c2b14b --- /dev/null +++ b/src/server/spec/plugins/stripe.ts @@ -0,0 +1,85 @@ +import type { OperationObject, SchemaObject } from '@scalar/openapi-types/3.2' + +import { jsonBody, jsonResponse } from '@/server/spec/components.js' +import { securedRequirement } from '@/server/spec/security.js' +import type { Translate } from '@/shared/translations/types.js' + +import { type OfficialPlugin, messageErrors, mountEndpoints } from './shared.js' + +const TAG = 'Stripe' + +export const stripeWebhookOperation = ({ + tag, + operationId, + t, +}: { + tag: string + operationId: string + t: Translate +}): OperationObject => { + const received: SchemaObject = { + type: 'object', + properties: { received: { type: 'boolean' } }, + required: ['received'], + } + return { + tags: [tag], + operationId, + summary: t('stripeWebhook'), + parameters: [{ name: 'stripe-signature', in: 'header', schema: { type: 'string' } }], + requestBody: { + ...jsonBody({ type: 'object', additionalProperties: true }), + description: t('stripeWebhookBody'), + }, + responses: { + '200': jsonResponse(t('stripeWebhookResult'), received), + '400': jsonResponse(t('errorPluginStripeWebhook400'), received), + }, + } +} + +export const stripe: OfficialPlugin = { + slug: '@payloadcms/plugin-stripe', + tag: TAG, + build: ({ config, ctx, options, t }) => { + const rest = options?.rest as { allowedMethods?: unknown } | undefined + const allowed = Array.isArray(rest?.allowedMethods) ? (rest.allowedMethods as string[]) : undefined + const restOperation: OperationObject = { + tags: [TAG], + operationId: 'stripeRest', + summary: t('stripeRest'), + requestBody: jsonBody({ + type: 'object', + properties: { + stripeMethod: { type: 'string', ...(allowed ? { enum: [...new Set(allowed)] } : {}) }, + stripeArgs: { type: 'array', items: {} }, + }, + required: ['stripeMethod', 'stripeArgs'], + }), + responses: { + '200': jsonResponse(t('stripeRestResult'), { + type: 'object', + properties: { data: {}, status: { type: 'integer', enum: [200] } }, + required: ['data', 'status'], + }), + ...messageErrors(['400', '401', '403'], t), + '404': jsonResponse(t('errorPluginStripeRest404'), { + type: 'object', + properties: { message: { type: 'string' }, status: { type: 'integer', enum: [404] } }, + required: ['message', 'status'], + }), + ...messageErrors(['500'], t), + }, + security: securedRequirement(), + } + return [ + { + paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => { + if (method !== 'post') return undefined + if (path === '/stripe/webhooks') return stripeWebhookOperation({ tag: TAG, operationId: 'stripeWebhooks', t }) + return path === '/stripe/rest' ? restOperation : undefined + }), + }, + ] + }, +} diff --git a/src/server/spec/security.ts b/src/server/spec/security.ts index cc21b58..adfba85 100644 --- a/src/server/spec/security.ts +++ b/src/server/spec/security.ts @@ -13,6 +13,7 @@ import type { import { isPlainObject } from '@/shared/utils.js' import { INTERACTIVE_SCHEME_NAME, SECURITY_SCHEME_NAME } from './components.js' +import { operationPlugin } from './filters.js' const HTTP_METHODS: HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete'] @@ -126,12 +127,14 @@ export const applySecurity = async ({ paths, slug, kind, + plugin, security, login = false, }: { paths: PathsObject slug?: string kind: OperationKind + plugin?: string security?: SecurityOption login?: boolean }): Promise => { @@ -145,7 +148,7 @@ export const applySecurity = async ({ if (login && op.security) op.security = withLogin(op.security) if (!security) continue const detected = op.security?.length ? 'secured' : 'public' - const decision = await security({ method, path, slug, kind, detected }) + const decision = await security({ method, path, slug, kind, plugin: operationPlugin(op, plugin), detected }) if (decision === 'public') delete op.security else if (decision === 'secured') op.security = securedRequirement(login) else if (Array.isArray(decision)) op.security = decision diff --git a/src/server/spec/tags.ts b/src/server/spec/tags.ts index e24f60b..c513e2e 100644 --- a/src/server/spec/tags.ts +++ b/src/server/spec/tags.ts @@ -5,12 +5,18 @@ import type { Translate } from '@/shared/translations/types.js' export const NAV_COLLECTIONS = 'Collections' export const NAV_GLOBALS = 'Globals' export const NAV_SYSTEM = 'System' +export const NAV_PLUGINS = 'Plugins' export const entityTagName = (base: string): string => base export const authTagName = (base: string): string => `${base} Auth` export const versionsTagName = (base: string): string => `${base} Versions` -const SYSTEM_TAG_SUMMARIES = { Jobs: 'tagJobs', Uploads: 'tagUploads', Access: 'tagAccess' } as const +const SYSTEM_TAG_SUMMARIES = { + Jobs: 'tagJobs', + Uploads: 'tagUploads', + Reorder: 'tagReorder', + Access: 'tagAccess', +} as const export type SystemTag = keyof typeof SYSTEM_TAG_SUMMARIES @@ -31,11 +37,19 @@ export interface TagHierarchyInput { collections: CollectionTagInfo[] globals: GlobalTagInfo[] systemTags: SystemTag[] + pluginTags?: string[] t: Translate nested: boolean } -export const buildTagHierarchy = ({ collections, globals, systemTags, t, nested }: TagHierarchyInput): TagObject[] => { +export const buildTagHierarchy = ({ + collections, + globals, + systemTags, + pluginTags = [], + t, + nested, +}: TagHierarchyInput): TagObject[] => { if (!nested) { return [ ...collections.map((c) => ({ name: entityTagName(c.base), description: c.description })), @@ -72,5 +86,12 @@ export const buildTagHierarchy = ({ collections, globals, systemTags, t, nested for (const name of systemTags) tags.push({ name, parent: NAV_SYSTEM, summary: t(SYSTEM_TAG_SUMMARIES[name]) }) } + const taken = new Set(tags.map((tag) => tag.name)) + const freePluginTags = pluginTags.filter((name) => !taken.has(name)) + if (freePluginTags.length > 0) { + tags.push({ name: NAV_PLUGINS, summary: t('tagPlugins'), description: t('tagPluginsDesc'), kind: 'nav' }) + for (const name of freePluginTags) tags.push({ name, parent: NAV_PLUGINS }) + } + return tags } diff --git a/src/shared/translations/locales/ar.ts b/src/shared/translations/locales/ar.ts index 61771f2..d78bdae 100644 --- a/src/shared/translations/locales/ar.ts +++ b/src/shared/translations/locales/ar.ts @@ -85,11 +85,25 @@ export const ar: PluginDefaultTranslationsObject = { uploadPayloadField: 'حقول {{schema}} بصيغة JSON. مثال: `{"alt":"A caption"}`.', fileServe: 'الملف', filePartial: 'جزء من الملف، لطلب `Range`', + paramFileVersion: 'يعرض الملف المحفوظ مع معرّف الإصدار هذا.', uploadInstructionsSummary: 'الحصول على تعليمات رفع ملف قبل حفظ المستند', uploadInstructionsResult: 'مكان إرسال بايتات الملف، وقيمة `file` التي تُرسل مع طلب الإنشاء أو التحديث', uploadStagePutSummary: 'إرسال بايتات الملف لرفع مؤقت', uploadStageDeleteSummary: 'حذف رفع مؤقت', uploadStageResult: 'تم، بدون محتوى', + paramPreferenceKey: 'مفتاح التفضيل، مثل `nav`.', + preferenceGet: 'الحصول على تفضيل للمستخدم الحالي', + preferenceGetResult: 'التفضيل المحفوظ، أو `value: null` إذا لم يكن موجودًا', + preferenceSet: 'حفظ تفضيل للمستخدم الحالي', + preferenceSetBody: 'أرسل القيمة في `value`. يُحفظ الجسم الذي لا يحتوي على `value` كاملًا كقيمة.', + preferenceSetResult: 'التفضيل المحفوظ', + preferenceDelete: 'حذف تفضيل للمستخدم الحالي', + preferenceDeleteResult: 'التفضيل المحذوف', + reorder: 'نقل المستندات في قائمة قابلة للترتيب', + reorderBody: + 'ينقل `docsToMove` بعد `target` مع `newKeyWillBe: "greater"`، أو قبله مع `"less"`. قيمة `orderableFieldName` هي `_order` لمجموعة قابلة للترتيب، أو حقل الترتيب لـ join قابل للترتيب. بدون `target.key` يمنح Payload مفاتيح الترتيب فقط للمستندات التي لا تملكها.', + reorderResult: 'مفاتيح الترتيب الجديدة، أو `message: "initial migration"` عندما عيّن Payload مفاتيح الترتيب لأول مرة', + errorReorder400: 'جسم الطلب غير صالح؛ السبب في `error`', error400: 'خطأ في التحقق أو الاستعلام (ValidationError، QueryError)', error401: 'غير مُصادَق عليه (AuthenticationError)', @@ -113,6 +127,71 @@ export const ar: PluginDefaultTranslationsObject = { tagJobs: 'المهام', tagUploads: 'الرفع', tagAccess: 'الوصول', + tagReorder: 'إعادة الترتيب', + tagPlugins: 'الإضافات', + tagPluginsDesc: 'نقاط نهاية تضيفها إضافات Payload الرسمية.', + errorPlugin400: 'طلب غير صالح', + errorPlugin401: 'غير مصادق', + errorPlugin403: 'محظور', + errorPlugin404: 'غير موجود', + errorPlugin500: 'خطأ داخلي في الخادم', + ecommerceAddItem: 'إضافة منتج إلى السلة', + ecommerceRemoveItem: 'إزالة منتج من السلة', + ecommerceUpdateItem: 'تغيير كمية منتج في السلة', + ecommerceClearCart: 'إزالة كل المنتجات من السلة', + ecommerceMergeCart: 'دمج سلة ضيف في هذه السلة', + ecommerceCartAccess: 'مسموح لمالك السلة، أو لسلة ضيف مع `secret` الخاص بها في جسم الطلب.', + ecommerceCartResult: 'السلة المحدّثة', + errorPluginEcommerce404: 'السلة غير موجودة أو لا يمكن الوصول إليها', + ecommerceQuantity: 'الكمية الجديدة، أو `{ "$inc": n }` لتغييرها بمقدار n.', + ecommerceInitiatePayment: 'بدء دفعة عبر `{{method}}`', + ecommerceConfirmOrder: 'تأكيد الدفع عبر `{{method}}` وإنشاء الطلب', + ecommercePaymentBody: + 'يستخدم `cartID` (مع `secret` لسلة ضيف) أو سلة المستخدم. بدون مستخدم يكون `customerEmail` مطلوبًا. قد يحتاج محوّل الدفع إلى حقول إضافية.', + ecommerceInitiateResult: 'بدأت عملية الدفع. يضيف المحوّل حقوله الخاصة، مثل client secret.', + ecommerceConfirmResult: 'تم إنشاء الطلب', + stripeWebhook: 'استقبال أحداث webhook من Stripe', + stripeWebhookBody: 'حدث Stripe الخام، موقّع في الترويسة `Stripe-Signature`. يستدعيه Stripe وليس عملاء API.', + stripeWebhookResult: 'تم استلام الحدث', + errorPluginStripeWebhook400: 'فشل التحقق من التوقيع', + stripeRest: 'استدعاء دالة مسموح بها من Stripe API', + stripeRestResult: 'نتيجة Stripe API', + errorPluginStripeRest404: 'أعادت Stripe API خطأ', + mcp: 'إرسال رسالة MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol عبر Streamable HTTP مع ردود JSON. الطلبات المجهولة تعمل؛ الأدوات المعروضة تعتمد على صلاحيات المستخدم. يجب على العملاء الذين يستخدمون إصدار بروتوكول من 2025 إرسال `Accept: application/json, text/event-stream`.', + mcpResult: 'رد JSON-RPC', + mcpOverrideAccess: 'تجاوز فحوص الوصول. للتطوير فقط.', + mcpGet: 'غير مدعوم: الخادم لا يفتح أي تدفق أحداث', + errorPluginMcpGet405: 'الطريقة غير مسموح بها، استخدم POST', + mcpProtocolVersion: 'إصدار بروتوكول MCP المتفق عليه، مثل `2025-06-18`.', + mcpResult202: 'مقبول: احتوى الجسم على إشعارات أو ردود فقط', + errorPluginMcp404: 'دالة MCP غير معروفة', + errorPluginMcp406: 'الترويسة `Accept` لا تتضمن `application/json` أو `text/event-stream`', + errorPluginMcp413: 'جسم الطلب كبير جدًا', + errorPluginMcp415: 'يجب أن يكون `Content-Type` هو `application/json`', + seoTitle: 'توليد عنوان الميتا', + seoDescription: 'توليد وصف الميتا', + seoUrl: 'توليد رابط المعاينة', + seoImage: 'توليد صورة الميتا', + seoBody: + 'المستند قيد التحرير: `collectionSlug` أو `globalSlug`، و`id` الخاص به، وبيانات `doc` الحالية. يُمرَّر إلى دالة التوليد لديك.', + seoResult: 'القيمة المولَّدة. سلسلة فارغة عند عدم تعيين دالة توليد.', + searchReindex: 'إعادة بناء فهرس البحث لبعض المجموعات', + searchReindexResult: 'ملخص إعادة الفهرسة', + tenantOptions: 'عرض المستأجرين الذين يمكن للمستخدم اختيارهم', + tenantOptionsResult: 'خيارات المستأجرين', + exportDownload: 'تصدير المستندات إلى ملف', + exportDownloadResult: 'ملف التصدير', + exportPreview: 'معاينة تصدير', + importPreview: 'معاينة ملف استيراد', + previewResult: 'صفحة من مستندات المعاينة', + importFileData: 'محتوى الملف بترميز base64.', + r2Upload: 'رفع ملف إلى R2 على أجزاء', + r2UploadDesc: + 'ثلاث خطوات على مسار واحد. البدء: أرسل `collection` و`fileName` و`fileType`. كل جزء: أضف `multipartId` و`multipartKey` و`multipartNumber` و`signedReceipt` وأرسل البايتات. الإنهاء: الشيء نفسه بدون `multipartNumber`، مع قائمة JSON بالأجزاء.', + r2UploadResult: 'بدأ الرفع، أو رُفع جزء، أو اكتمل الرفع (مفتاح الكائن كنص)', + errorStorageR2412: 'يوجد ملف بالفعل عند هذا المفتاح', localizationHeading: 'الترجمة', localizationNote: diff --git a/src/shared/translations/locales/az.ts b/src/shared/translations/locales/az.ts index 6bbe406..453d9fa 100644 --- a/src/shared/translations/locales/az.ts +++ b/src/shared/translations/locales/az.ts @@ -88,12 +88,27 @@ export const az: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-a çevrilmiş {{schema}} sahələri. Nümunə: `{"alt":"A caption"}`.', fileServe: 'Fayl', filePartial: 'Faylın bir hissəsi, `Range` sorğusu üçün', + paramFileVersion: 'Bu versiya ID-si ilə saxlanmış faylı qaytarır.', uploadInstructionsSummary: 'Sənədi saxlamazdan əvvəl fayl yükləmək üçün təlimat alın', uploadInstructionsResult: 'Fayl baytlarının hara göndəriləcəyi və yaratma və ya yeniləmə sorğusu ilə göndəriləcək `file` dəyəri', uploadStagePutSummary: 'Mərhələli yükləmə üçün fayl baytlarını göndərin', uploadStageDeleteSummary: 'Mərhələli yükləməni silin', uploadStageResult: 'Hazırdır, məzmun yoxdur', + paramPreferenceKey: 'Seçimin açarı, məs. `nav`.', + preferenceGet: 'Cari istifadəçinin seçimini al', + preferenceGetResult: 'Saxlanmış seçim, yoxdursa `value: null`', + preferenceSet: 'Cari istifadəçinin seçimini saxla', + preferenceSetBody: 'Dəyəri `value` daxilində göndərin. `value` olmayan gövdə bütövlükdə dəyər kimi saxlanılır.', + preferenceSetResult: 'Saxlanmış seçim', + preferenceDelete: 'Cari istifadəçinin seçimini sil', + preferenceDeleteResult: 'Silinmiş seçim', + reorder: 'Sıralana bilən siyahıda sənədləri köçür', + reorderBody: + '`docsToMove` sənədlərini `newKeyWillBe: "greater"` ilə `target`-dən sonraya, `"less"` ilə ondan əvvələ köçürür. `orderableFieldName` sıralana bilən kolleksiya üçün `_order`, sıralana bilən join üçün isə onun sıra sahəsidir. `target.key` olmadan Payload yalnız sıra açarı olmayan sənədlərə sıra açarı verir.', + reorderResult: + 'Yeni sıra açarları və ya Payload sıra açarlarını ilk dəfə təyin etdikdə `message: "initial migration"`', + errorReorder400: 'Yanlış sorğu gövdəsi; səbəb `error` daxilindədir', error400: 'Doğrulama və ya sorğu xətası (ValidationError, QueryError)', error401: 'Autentifikasiya olunmayıb (AuthenticationError)', @@ -117,6 +132,72 @@ export const az: PluginDefaultTranslationsObject = { tagJobs: 'İşlər', tagUploads: 'Yükləmələr', tagAccess: 'Giriş', + tagReorder: 'Sıralama', + tagPlugins: 'Pluginlər', + tagPluginsDesc: 'Rəsmi Payload pluginlərinin əlavə etdiyi endpointlər.', + errorPlugin400: 'Yanlış sorğu', + errorPlugin401: 'Autentifikasiya olunmayıb', + errorPlugin403: 'Qadağandır', + errorPlugin404: 'Tapılmadı', + errorPlugin500: 'Daxili server xətası', + ecommerceAddItem: 'Səbətə məhsul əlavə et', + ecommerceRemoveItem: 'Səbətdən məhsulu sil', + ecommerceUpdateItem: 'Səbətdəki məhsulun miqdarını dəyiş', + ecommerceClearCart: 'Səbətdəki bütün məhsulları sil', + ecommerceMergeCart: 'Qonaq səbətini bu səbətlə birləşdir', + ecommerceCartAccess: 'Səbətin sahibinə və ya sorğu gövdəsində `secret` olan qonaq səbətinə icazə verilir.', + ecommerceCartResult: 'Yenilənmiş səbət', + errorPluginEcommerce404: 'Səbət tapılmadı və ya əlçatan deyil', + ecommerceQuantity: 'Yeni miqdar və ya onu n qədər dəyişmək üçün `{ "$inc": n }`.', + ecommerceInitiatePayment: '`{{method}}` ilə ödənişə başla', + ecommerceConfirmOrder: '`{{method}}` ilə ödənişi təsdiqlə və sifariş yarat', + ecommercePaymentBody: + '`cartID` (qonaq səbəti üçün `secret` ilə) və ya istifadəçinin səbətini istifadə edir. İstifadəçi olmadıqda `customerEmail` məcburidir. Ödəniş adapteri əlavə sahələr tələb edə bilər.', + ecommerceInitiateResult: 'Ödəniş başladı. Adapter öz sahələrini əlavə edir, məs. client secret.', + ecommerceConfirmResult: 'Sifariş yaradıldı', + stripeWebhook: 'Stripe webhook hadisələrini qəbul et', + stripeWebhookBody: + '`Stripe-Signature` başlığında imzalanmış xam Stripe hadisəsi. API müştəriləri deyil, Stripe çağırır.', + stripeWebhookResult: 'Hadisə qəbul edildi', + errorPluginStripeWebhook400: 'İmza yoxlaması uğursuz oldu', + stripeRest: 'İcazə verilən Stripe API metodunu çağır', + stripeRestResult: 'Stripe API nəticəsi', + errorPluginStripeRest404: 'Stripe API xəta qaytardı', + mcp: 'MCP JSON-RPC mesajı göndər', + mcpDesc: + 'Streamable HTTP üzərindən JSON cavablı Model Context Protocol. Anonim sorğular işləyir; siyahıdakı alətlər istifadəçinin hüquqlarından asılıdır. 2025-ci il protokol versiyasından istifadə edən müştərilər `Accept: application/json, text/event-stream` göndərməlidir.', + mcpResult: 'JSON-RPC cavabı', + mcpOverrideAccess: 'Giriş yoxlamalarını ötür. Yalnız inkişaf üçün.', + mcpGet: 'Dəstəklənmir: server hadisə axını açmır', + errorPluginMcpGet405: 'Metoda icazə verilmir, POST istifadə edin', + mcpProtocolVersion: 'Razılaşdırılmış MCP protokol versiyası, məs. `2025-06-18`.', + mcpResult202: 'Qəbul edildi: gövdədə yalnız bildirişlər və ya cavablar var idi', + errorPluginMcp404: 'Naməlum MCP metodu', + errorPluginMcp406: '`Accept` başlığında `application/json` və ya `text/event-stream` yoxdur', + errorPluginMcp413: 'Sorğu gövdəsi çox böyükdür', + errorPluginMcp415: '`Content-Type` `application/json` olmalıdır', + seoTitle: 'Meta başlığı yarat', + seoDescription: 'Meta təsviri yarat', + seoUrl: 'Önizləmə URL-ni yarat', + seoImage: 'Meta şəkli yarat', + seoBody: + 'Redaktə olunan sənəd: `collectionSlug` və ya `globalSlug`, onun `id` dəyəri və cari `doc` məlumatı. Generate funksiyanıza ötürülür.', + seoResult: 'Yaradılmış dəyər. Generate funksiyası təyin edilməyibsə boş sətir.', + searchReindex: 'Bəzi kolleksiyalar üçün axtarış indeksini yenidən qur', + searchReindexResult: 'Yenidən indeksləmə xülasəsi', + tenantOptions: 'İstifadəçinin seçə biləcəyi icarəçiləri sırala', + tenantOptionsResult: 'İcarəçi seçimləri', + exportDownload: 'Sənədləri fayla ixrac et', + exportDownloadResult: 'İxrac faylı', + exportPreview: 'İxraca önizləmə', + importPreview: 'İdxal faylına önizləmə', + previewResult: 'Önizləmə sənədlərinin bir səhifəsi', + importFileData: 'Faylın məzmunu, base64 ilə kodlanmış.', + r2Upload: 'Faylı R2-yə hissə-hissə yüklə', + r2UploadDesc: + 'Bir marşrutda üç addım. Başlanğıc: `collection`, `fileName` və `fileType` göndərin. Hər hissə: `multipartId`, `multipartKey`, `multipartNumber` və `signedReceipt` əlavə edib baytları göndərin. Tamamlama: eynisi `multipartNumber` olmadan, hissələrin JSON siyahısı ilə.', + r2UploadResult: 'Yükləmə başladı, hissə yükləndi və ya yükləmə tamamlandı (obyekt açarı mətn kimi)', + errorStorageR2412: 'Bu açarla artıq fayl mövcuddur', localizationHeading: 'Lokallaşdırma', localizationNote: diff --git a/src/shared/translations/locales/bg.ts b/src/shared/translations/locales/bg.ts index 84106cd..66900af 100644 --- a/src/shared/translations/locales/bg.ts +++ b/src/shared/translations/locales/bg.ts @@ -86,12 +86,27 @@ export const bg: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-сериализирани {{schema}} полета. Пример: `{"alt":"A caption"}`.', fileServe: 'Файлът', filePartial: 'Част от файла при заявка с `Range`', + paramFileVersion: 'Връща файла, запазен с това ID на версия.', uploadInstructionsSummary: 'Получаване на инструкции за качване на файл преди запис на документа', uploadInstructionsResult: 'Къде да се изпратят байтовете на файла и стойността `file` за заявката за създаване или обновяване', uploadStagePutSummary: 'Изпращане на байтовете на файла за временно качване', uploadStageDeleteSummary: 'Изтриване на временно качване', uploadStageResult: 'Готово, без съдържание', + paramPreferenceKey: 'Ключът на настройката, напр. `nav`.', + preferenceGet: 'Вземане на настройка на текущия потребител', + preferenceGetResult: 'Запазената настройка или `value: null`, ако няма такава', + preferenceSet: 'Запазване на настройка на текущия потребител', + preferenceSetBody: 'Изпратете стойността в `value`. Тяло без `value` се запазва изцяло като стойност.', + preferenceSetResult: 'Запазената настройка', + preferenceDelete: 'Изтриване на настройка на текущия потребител', + preferenceDeleteResult: 'Изтритата настройка', + reorder: 'Преместване на документи в подреждаем списък', + reorderBody: + 'Премества `docsToMove` след `target` при `newKeyWillBe: "greater"` или преди него при `"less"`. `orderableFieldName` е `_order` за подреждаема колекция или полето за ред на подреждаем join. Без `target.key` Payload само задава ключове за ред на документите, които нямат такива.', + reorderResult: + 'Новите ключове за ред или `message: "initial migration"`, когато Payload за първи път е задал ключовете за ред', + errorReorder400: 'Невалидно тяло на заявката; причината е в `error`', error400: 'Грешка при валидация или заявка (ValidationError, QueryError)', error401: 'Не сте удостоверени (AuthenticationError)', @@ -115,6 +130,72 @@ export const bg: PluginDefaultTranslationsObject = { tagJobs: 'Задачи', tagUploads: 'Качвания', tagAccess: 'Достъп', + tagReorder: 'Подредба', + tagPlugins: 'Плъгини', + tagPluginsDesc: 'Endpoint-и, добавени от официалните плъгини на Payload.', + errorPlugin400: 'Невалидна заявка', + errorPlugin401: 'Не сте удостоверени', + errorPlugin403: 'Забранено', + errorPlugin404: 'Не е намерено', + errorPlugin500: 'Вътрешна грешка на сървъра', + ecommerceAddItem: 'Добавяне на артикул в количката', + ecommerceRemoveItem: 'Премахване на артикул от количката', + ecommerceUpdateItem: 'Промяна на количеството на артикул в количката', + ecommerceClearCart: 'Премахване на всички артикули от количката', + ecommerceMergeCart: 'Сливане на гост количка с тази количка', + ecommerceCartAccess: 'Разрешено за собственика на количката или за гост количка с нейния `secret` в тялото.', + ecommerceCartResult: 'Обновената количка', + errorPluginEcommerce404: 'Количката не е намерена или е недостъпна', + ecommerceQuantity: 'Ново количество или `{ "$inc": n }`, за да се промени с n.', + ecommerceInitiatePayment: 'Започване на плащане с `{{method}}`', + ecommerceConfirmOrder: 'Потвърждаване на плащането с `{{method}}` и създаване на поръчката', + ecommercePaymentBody: + 'Използва `cartID` (с `secret` за гост количка) или количката на потребителя. Без потребител `customerEmail` е задължителен. Адаптерът за плащане може да изисква още полета.', + ecommerceInitiateResult: 'Плащането е започнато. Адаптерът добавя свои полета, напр. client secret.', + ecommerceConfirmResult: 'Поръчката е създадена', + stripeWebhook: 'Получаване на webhook събития от Stripe', + stripeWebhookBody: + 'Необработеното събитие от Stripe, подписано в заглавката `Stripe-Signature`. Извиква се от Stripe, не от API клиенти.', + stripeWebhookResult: 'Събитието е получено', + errorPluginStripeWebhook400: 'Проверката на подписа е неуспешна', + stripeRest: 'Извикване на разрешен метод от API на Stripe', + stripeRestResult: 'Резултатът от API на Stripe', + errorPluginStripeRest404: 'API на Stripe върна грешка', + mcp: 'Изпращане на MCP JSON-RPC съобщение', + mcpDesc: + 'Model Context Protocol през Streamable HTTP, с JSON отговори. Анонимните заявки работят; изброените инструменти зависят от правата на потребителя. Клиентите с версия на протокола от 2025 г. трябва да изпращат `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC отговор', + mcpOverrideAccess: 'Пропускане на проверките за достъп. Само за разработка.', + mcpGet: 'Не се поддържа: сървърът не отваря поток от събития', + errorPluginMcpGet405: 'Методът не е разрешен, използвайте POST', + mcpProtocolVersion: 'Договорената версия на протокола MCP, напр. `2025-06-18`.', + mcpResult202: 'Прието: тялото съдържа само известия или отговори', + errorPluginMcp404: 'Непознат MCP метод', + errorPluginMcp406: 'Заглавката `Accept` не съдържа `application/json` или `text/event-stream`', + errorPluginMcp413: 'Тялото на заявката е твърде голямо', + errorPluginMcp415: '`Content-Type` трябва да е `application/json`', + seoTitle: 'Генериране на мета заглавие', + seoDescription: 'Генериране на мета описание', + seoUrl: 'Генериране на URL за преглед', + seoImage: 'Генериране на мета изображение', + seoBody: + 'Редактираният документ: `collectionSlug` или `globalSlug`, неговото `id` и текущите данни в `doc`. Подава се на вашата функция за генериране.', + seoResult: 'Генерираната стойност. Празен низ, ако няма зададена функция за генериране.', + searchReindex: 'Преизграждане на индекса за търсене за някои колекции', + searchReindexResult: 'Обобщение на преиндексирането', + tenantOptions: 'Списък с наемателите, които потребителят може да избере', + tenantOptionsResult: 'Опции за наематели', + exportDownload: 'Експортиране на документи във файл', + exportDownloadResult: 'Файлът с експорта', + exportPreview: 'Преглед на експорт', + importPreview: 'Преглед на файл за импорт', + previewResult: 'Страница с документи за преглед', + importFileData: 'Съдържание на файла, кодирано в base64.', + r2Upload: 'Качване на файл в R2 на части', + r2UploadDesc: + 'Три стъпки на един маршрут. Начало: изпратете `collection`, `fileName` и `fileType`. Всяка част: добавете `multipartId`, `multipartKey`, `multipartNumber` и `signedReceipt` и изпратете байтовете. Завършване: същото без `multipartNumber`, с JSON списъка на частите.', + r2UploadResult: 'Качването е започнато, частта е качена или качването е завършено (ключът на обекта като текст)', + errorStorageR2412: 'Вече има файл с този ключ', localizationHeading: 'Локализация', localizationNote: diff --git a/src/shared/translations/locales/bnBd.ts b/src/shared/translations/locales/bnBd.ts index 6da1c6e..ed56f53 100644 --- a/src/shared/translations/locales/bnBd.ts +++ b/src/shared/translations/locales/bnBd.ts @@ -86,11 +86,25 @@ export const bnBd: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-স্ট্রিংফাইড {{schema}} ফিল্ড। উদাহরণ: `{"alt":"A caption"}`।', fileServe: 'ফাইলটি', filePartial: 'ফাইলের একটি অংশ, `Range` রিকোয়েস্টের জন্য', + paramFileVersion: 'এই ভার্সন ID-র সাথে সংরক্ষিত ফাইলটি দেয়।', uploadInstructionsSummary: 'ডকুমেন্ট সেভ করার আগে ফাইল আপলোডের নির্দেশনা নিন', uploadInstructionsResult: 'ফাইলের বাইট কোথায় পাঠাতে হবে এবং তৈরি বা আপডেট রিকোয়েস্টে কোন `file` মান পাঠাতে হবে', uploadStagePutSummary: 'অস্থায়ী আপলোডের জন্য ফাইলের বাইট পাঠান', uploadStageDeleteSummary: 'অস্থায়ী আপলোড মুছুন', uploadStageResult: 'সম্পন্ন, কোনো কনটেন্ট নেই', + paramPreferenceKey: 'প্রেফারেন্সের কী, যেমন `nav`।', + preferenceGet: 'বর্তমান ইউজারের একটি প্রেফারেন্স নিন', + preferenceGetResult: 'সেভ করা প্রেফারেন্স, অথবা না থাকলে `value: null`', + preferenceSet: 'বর্তমান ইউজারের একটি প্রেফারেন্স সেভ করুন', + preferenceSetBody: 'মানটি `value`-তে পাঠান। `value` ছাড়া বডি পুরোটাই মান হিসেবে সেভ হয়।', + preferenceSetResult: 'সেভ করা প্রেফারেন্স', + preferenceDelete: 'বর্তমান ইউজারের একটি প্রেফারেন্স ডিলিট করুন', + preferenceDeleteResult: 'ডিলিট করা প্রেফারেন্স', + reorder: 'সাজানোযোগ্য তালিকায় ডকুমেন্ট সরান', + reorderBody: + '`newKeyWillBe: "greater"` হলে `docsToMove`-কে `target`-এর পরে, `"less"` হলে আগে সরায়। সাজানোযোগ্য কালেকশনের জন্য `orderableFieldName` হলো `_order`, আর সাজানোযোগ্য join-এর জন্য তার অর্ডার ফিল্ড। `target.key` না থাকলে Payload শুধু যেসব ডকুমেন্টের অর্ডার কী নেই সেগুলোকে অর্ডার কী দেয়।', + reorderResult: 'নতুন অর্ডার কী, অথবা Payload প্রথমবার অর্ডার কী সেট করলে `message: "initial migration"`', + errorReorder400: 'রিকোয়েস্ট বডি অবৈধ; কারণ `error`-এ আছে', error400: 'ভ্যালিডেশন বা কোয়েরি ত্রুটি (ValidationError, QueryError)', error401: 'প্রমাণীকৃত নয় (AuthenticationError)', @@ -113,6 +127,71 @@ export const bnBd: PluginDefaultTranslationsObject = { tagJobs: 'জব', tagUploads: 'আপলোড', tagAccess: 'অ্যাক্সেস', + tagReorder: 'পুনর্বিন্যাস', + tagPlugins: 'প্লাগইন', + tagPluginsDesc: 'অফিসিয়াল Payload প্লাগইনের যোগ করা এন্ডপয়েন্ট।', + errorPlugin400: 'অবৈধ অনুরোধ', + errorPlugin401: 'প্রমাণীকৃত নয়', + errorPlugin403: 'নিষিদ্ধ', + errorPlugin404: 'পাওয়া যায়নি', + errorPlugin500: 'সার্ভারের অভ্যন্তরীণ ত্রুটি', + ecommerceAddItem: 'কার্টে একটি পণ্য যোগ করুন', + ecommerceRemoveItem: 'কার্ট থেকে একটি পণ্য সরান', + ecommerceUpdateItem: 'কার্টের পণ্যের পরিমাণ বদলান', + ecommerceClearCart: 'কার্টের সব পণ্য সরান', + ecommerceMergeCart: 'অতিথি কার্টকে এই কার্টের সাথে একত্র করুন', + ecommerceCartAccess: 'কার্টের মালিকের জন্য, অথবা বডিতে `secret` সহ অতিথি কার্টের জন্য অনুমোদিত।', + ecommerceCartResult: 'হালনাগাদ করা কার্ট', + errorPluginEcommerce404: 'কার্ট পাওয়া যায়নি বা অ্যাক্সেসযোগ্য নয়', + ecommerceQuantity: 'নতুন পরিমাণ, অথবা n পরিমাণ বদলাতে `{ "$inc": n }`।', + ecommerceInitiatePayment: '`{{method}}` দিয়ে পেমেন্ট শুরু করুন', + ecommerceConfirmOrder: '`{{method}}` দিয়ে পেমেন্ট নিশ্চিত করে অর্ডার তৈরি করুন', + ecommercePaymentBody: + '`cartID` (অতিথি কার্টের জন্য `secret` সহ) অথবা ব্যবহারকারীর কার্ট ব্যবহার করে। ব্যবহারকারী না থাকলে `customerEmail` আবশ্যক। পেমেন্ট অ্যাডাপ্টারের আরও ফিল্ড লাগতে পারে।', + ecommerceInitiateResult: 'পেমেন্ট শুরু হয়েছে। অ্যাডাপ্টার নিজের ফিল্ড যোগ করে, যেমন client secret।', + ecommerceConfirmResult: 'অর্ডার তৈরি হয়েছে', + stripeWebhook: 'Stripe webhook ইভেন্ট গ্রহণ করুন', + stripeWebhookBody: '`Stripe-Signature` হেডারে স্বাক্ষরিত কাঁচা Stripe ইভেন্ট। API ক্লায়েন্ট নয়, Stripe এটি কল করে।', + stripeWebhookResult: 'ইভেন্ট পাওয়া গেছে', + errorPluginStripeWebhook400: 'স্বাক্ষর যাচাই ব্যর্থ হয়েছে', + stripeRest: 'অনুমোদিত একটি Stripe API মেথড কল করুন', + stripeRestResult: 'Stripe API-এর ফলাফল', + errorPluginStripeRest404: 'Stripe API একটি ত্রুটি ফেরত দিয়েছে', + mcp: 'একটি MCP JSON-RPC বার্তা পাঠান', + mcpDesc: + 'Streamable HTTP-এর উপর Model Context Protocol, JSON উত্তরসহ। বেনামি অনুরোধ কাজ করে; তালিকাভুক্ত টুল ব্যবহারকারীর অ্যাক্সেসের উপর নির্ভর করে। ২০২৫ সালের প্রোটোকল ভার্সন ব্যবহারকারী ক্লায়েন্টকে `Accept: application/json, text/event-stream` পাঠাতে হবে।', + mcpResult: 'JSON-RPC উত্তর', + mcpOverrideAccess: 'অ্যাক্সেস যাচাই বাদ দিন। শুধু ডেভেলপমেন্টের জন্য।', + mcpGet: 'সমর্থিত নয়: সার্ভার কোনো ইভেন্ট স্ট্রিম খোলে না', + errorPluginMcpGet405: 'মেথড অনুমোদিত নয়, POST ব্যবহার করুন', + mcpProtocolVersion: 'নির্ধারিত MCP প্রোটোকল ভার্সন, যেমন `2025-06-18`।', + mcpResult202: 'গৃহীত: বডিতে শুধু নোটিফিকেশন বা রেসপন্স ছিল', + errorPluginMcp404: 'অজানা MCP মেথড', + errorPluginMcp406: '`Accept` হেডারে `application/json` বা `text/event-stream` নেই', + errorPluginMcp413: 'রিকোয়েস্ট বডি খুব বড়', + errorPluginMcp415: '`Content-Type` অবশ্যই `application/json` হতে হবে', + seoTitle: 'মেটা শিরোনাম তৈরি করুন', + seoDescription: 'মেটা বিবরণ তৈরি করুন', + seoUrl: 'প্রিভিউ URL তৈরি করুন', + seoImage: 'মেটা ছবি তৈরি করুন', + seoBody: + 'সম্পাদনাধীন ডকুমেন্ট: `collectionSlug` বা `globalSlug`, এর `id` এবং বর্তমান `doc` ডেটা। আপনার জেনারেট ফাংশনে পাঠানো হয়।', + seoResult: 'তৈরি হওয়া মান। জেনারেট ফাংশন সেট না থাকলে খালি স্ট্রিং।', + searchReindex: 'কিছু কালেকশনের সার্চ ইনডেক্স আবার তৈরি করুন', + searchReindexResult: 'রিইনডেক্সের সারাংশ', + tenantOptions: 'ব্যবহারকারী যে টেন্যান্টগুলো বেছে নিতে পারেন তার তালিকা', + tenantOptionsResult: 'টেন্যান্ট অপশন', + exportDownload: 'ডকুমেন্ট একটি ফাইলে এক্সপোর্ট করুন', + exportDownloadResult: 'এক্সপোর্ট ফাইল', + exportPreview: 'এক্সপোর্টের প্রিভিউ', + importPreview: 'ইমপোর্ট ফাইলের প্রিভিউ', + previewResult: 'প্রিভিউ ডকুমেন্টের একটি পৃষ্ঠা', + importFileData: 'ফাইলের বিষয়বস্তু, base64-এ এনকোড করা।', + r2Upload: 'R2-এ অংশে অংশে ফাইল আপলোড করুন', + r2UploadDesc: + 'একই রুটে তিন ধাপ। শুরু: `collection`, `fileName` ও `fileType` পাঠান। প্রতিটি অংশ: `multipartId`, `multipartKey`, `multipartNumber` ও `signedReceipt` যোগ করে বাইট পাঠান। শেষ: `multipartNumber` ছাড়া একই, অংশগুলোর JSON তালিকাসহ।', + r2UploadResult: 'আপলোড শুরু, অংশ আপলোড বা আপলোড সম্পন্ন (অবজেক্ট কী টেক্সট হিসেবে)', + errorStorageR2412: 'এই কী-তে ইতিমধ্যে একটি ফাইল আছে', localizationHeading: 'লোকালাইজেশন', localizationNote: diff --git a/src/shared/translations/locales/bnIn.ts b/src/shared/translations/locales/bnIn.ts index f8ecfec..f7b8a2e 100644 --- a/src/shared/translations/locales/bnIn.ts +++ b/src/shared/translations/locales/bnIn.ts @@ -84,11 +84,25 @@ export const bnIn: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-স্ট্রিংকৃত {{schema}} ফিল্ড। উদাহরণ: `{"alt":"A caption"}`।', fileServe: 'ফাইলটি', filePartial: 'ফাইলের একটি অংশ, `Range` অনুরোধের জন্য', + paramFileVersion: 'এই সংস্করণ ID-র সঙ্গে সংরক্ষিত ফাইলটি দেয়।', uploadInstructionsSummary: 'নথি সংরক্ষণের আগে ফাইল আপলোডের নির্দেশনা নিন', uploadInstructionsResult: 'ফাইলের বাইট কোথায় পাঠাতে হবে এবং তৈরি বা হালনাগাদ অনুরোধে কোন `file` মান পাঠাতে হবে', uploadStagePutSummary: 'অস্থায়ী আপলোডের জন্য ফাইলের বাইট পাঠান', uploadStageDeleteSummary: 'অস্থায়ী আপলোড মুছুন', uploadStageResult: 'সম্পন্ন, কোনো বিষয়বস্তু নেই', + paramPreferenceKey: 'পছন্দের কী, যেমন `nav`।', + preferenceGet: 'বর্তমান ব্যবহারকারীর একটি পছন্দ নিন', + preferenceGetResult: 'সংরক্ষিত পছন্দ, অথবা না থাকলে `value: null`', + preferenceSet: 'বর্তমান ব্যবহারকারীর একটি পছন্দ সংরক্ষণ করুন', + preferenceSetBody: 'মানটি `value`-তে পাঠান। `value` ছাড়া বডি পুরোটাই মান হিসেবে সংরক্ষিত হয়।', + preferenceSetResult: 'সংরক্ষিত পছন্দ', + preferenceDelete: 'বর্তমান ব্যবহারকারীর একটি পছন্দ মুছুন', + preferenceDeleteResult: 'মুছে ফেলা পছন্দ', + reorder: 'সাজানোযোগ্য তালিকায় নথি সরান', + reorderBody: + '`newKeyWillBe: "greater"` হলে `docsToMove`-কে `target`-এর পরে, `"less"` হলে আগে সরায়। সাজানোযোগ্য কালেকশনের জন্য `orderableFieldName` হল `_order`, আর সাজানোযোগ্য join-এর জন্য তার ক্রম ফিল্ড। `target.key` না থাকলে Payload কেবল যেসব নথির ক্রম কী নেই সেগুলিকে ক্রম কী দেয়।', + reorderResult: 'নতুন ক্রম কী, অথবা Payload প্রথমবার ক্রম কী সেট করলে `message: "initial migration"`', + errorReorder400: 'অনুরোধের বডি অবৈধ; কারণ `error`-এ আছে', error400: 'যাচাই বা কোয়েরি ত্রুটি (ValidationError, QueryError)', error401: 'প্রমাণীকৃত নয় (AuthenticationError)', @@ -111,6 +125,70 @@ export const bnIn: PluginDefaultTranslationsObject = { tagJobs: 'জব', tagUploads: 'আপলোড', tagAccess: 'অ্যাক্সেস', + tagReorder: 'পুনর্বিন্যাস', + tagPlugins: 'প্লাগইন', + tagPluginsDesc: 'অফিসিয়াল Payload প্লাগইনের যোগ করা এন্ডপয়েন্ট।', + errorPlugin400: 'অবৈধ অনুরোধ', + errorPlugin401: 'প্রমাণীকৃত নয়', + errorPlugin403: 'নিষিদ্ধ', + errorPlugin404: 'পাওয়া যায়নি', + errorPlugin500: 'সার্ভারের অভ্যন্তরীণ ত্রুটি', + ecommerceAddItem: 'কার্টে একটি পণ্য যোগ করুন', + ecommerceRemoveItem: 'কার্ট থেকে একটি পণ্য সরান', + ecommerceUpdateItem: 'কার্টের পণ্যের পরিমাণ বদলান', + ecommerceClearCart: 'কার্টের সব পণ্য সরান', + ecommerceMergeCart: 'অতিথি কার্টকে এই কার্টের সাথে একত্র করুন', + ecommerceCartAccess: 'কার্টের মালিকের জন্য, অথবা বডিতে `secret` সহ অতিথি কার্টের জন্য অনুমোদিত।', + ecommerceCartResult: 'আপডেট করা কার্ট', + errorPluginEcommerce404: 'কার্ট পাওয়া যায়নি বা অ্যাক্সেসযোগ্য নয়', + ecommerceQuantity: 'নতুন পরিমাণ, অথবা n পরিমাণ বদলাতে `{ "$inc": n }`।', + ecommerceInitiatePayment: '`{{method}}` দিয়ে পেমেন্ট শুরু করুন', + ecommerceConfirmOrder: '`{{method}}` দিয়ে পেমেন্ট নিশ্চিত করে অর্ডার তৈরি করুন', + ecommercePaymentBody: + '`cartID` (অতিথি কার্টের জন্য `secret` সহ) অথবা ব্যবহারকারীর কার্ট ব্যবহার করে। ব্যবহারকারী না থাকলে `customerEmail` আবশ্যক। পেমেন্ট অ্যাডাপ্টারের আরও ফিল্ড লাগতে পারে।', + ecommerceInitiateResult: 'পেমেন্ট শুরু হয়েছে। অ্যাডাপ্টার নিজের ফিল্ড যোগ করে, যেমন client secret।', + ecommerceConfirmResult: 'অর্ডার তৈরি হয়েছে', + stripeWebhook: 'Stripe webhook ইভেন্ট গ্রহণ করুন', + stripeWebhookBody: '`Stripe-Signature` হেডারে স্বাক্ষরিত কাঁচা Stripe ইভেন্ট। API ক্লায়েন্ট নয়, Stripe এটি কল করে।', + stripeWebhookResult: 'ইভেন্ট পাওয়া গেছে', + errorPluginStripeWebhook400: 'স্বাক্ষর যাচাই ব্যর্থ হয়েছে', + stripeRest: 'অনুমোদিত একটি Stripe API মেথড কল করুন', + stripeRestResult: 'Stripe API-এর ফলাফল', + errorPluginStripeRest404: 'Stripe API একটি ত্রুটি ফেরত দিয়েছে', + mcp: 'একটি MCP JSON-RPC বার্তা পাঠান', + mcpDesc: + 'Streamable HTTP-এর উপর Model Context Protocol, JSON উত্তরসহ। বেনামি অনুরোধ কাজ করে; তালিকাভুক্ত টুল ব্যবহারকারীর অ্যাক্সেসের উপর নির্ভর করে। ২০২৫ সালের প্রোটোকল সংস্করণ ব্যবহারকারী ক্লায়েন্টদের `Accept: application/json, text/event-stream` পাঠাতে হবে।', + mcpResult: 'JSON-RPC উত্তর', + mcpOverrideAccess: 'অ্যাক্সেস যাচাই বাদ দিন। শুধু ডেভেলপমেন্টের জন্য।', + mcpGet: 'সমর্থিত নয়: সার্ভার কোনো ইভেন্ট স্ট্রিম খোলে না', + errorPluginMcpGet405: 'মেথড অনুমোদিত নয়, POST ব্যবহার করুন', + mcpProtocolVersion: 'নির্ধারিত MCP প্রোটোকল সংস্করণ, যেমন `2025-06-18`।', + mcpResult202: 'গৃহীত: বডিতে কেবল বিজ্ঞপ্তি বা প্রতিক্রিয়া ছিল', + errorPluginMcp404: 'অজানা MCP মেথড', + errorPluginMcp406: '`Accept` হেডারে `application/json` বা `text/event-stream` নেই', + errorPluginMcp413: 'অনুরোধের বডি খুব বড়', + errorPluginMcp415: '`Content-Type` অবশ্যই `application/json` হতে হবে', + seoTitle: 'মেটা শিরোনাম তৈরি করুন', + seoDescription: 'মেটা বিবরণ তৈরি করুন', + seoUrl: 'প্রিভিউ URL তৈরি করুন', + seoImage: 'মেটা ছবি তৈরি করুন', + seoBody: 'সম্পাদনাধীন নথি: `collectionSlug` বা `globalSlug`, এর `id` এবং বর্তমান `doc` ডেটা। আপনার জেনারেট ফাংশনে পাঠানো হয়।', + seoResult: 'তৈরি হওয়া মান। জেনারেট ফাংশন সেট না থাকলে খালি স্ট্রিং।', + searchReindex: 'কিছু কালেকশনের সার্চ ইনডেক্স আবার তৈরি করুন', + searchReindexResult: 'রিইনডেক্সের সারাংশ', + tenantOptions: 'ব্যবহারকারী যে টেন্যান্টগুলো বেছে নিতে পারেন তার তালিকা', + tenantOptionsResult: 'টেন্যান্ট অপশন', + exportDownload: 'নথি একটি ফাইলে এক্সপোর্ট করুন', + exportDownloadResult: 'এক্সপোর্ট ফাইল', + exportPreview: 'এক্সপোর্টের প্রিভিউ', + importPreview: 'ইমপোর্ট ফাইলের প্রিভিউ', + previewResult: 'প্রিভিউ নথিের একটি পৃষ্ঠা', + importFileData: 'ফাইলের বিষয়বস্তু, base64-এ এনকোড করা।', + r2Upload: 'R2-এ অংশে অংশে ফাইল আপলোড করুন', + r2UploadDesc: + 'একই রুটে তিন ধাপ। শুরু: `collection`, `fileName` ও `fileType` পাঠান। প্রতিটি অংশ: `multipartId`, `multipartKey`, `multipartNumber` ও `signedReceipt` যোগ করে বাইট পাঠান। শেষ: `multipartNumber` ছাড়া একই, অংশগুলোর JSON তালিকাসহ।', + r2UploadResult: 'আপলোড শুরু, অংশ আপলোড বা আপলোড সম্পন্ন (অবজেক্ট কী টেক্সট হিসেবে)', + errorStorageR2412: 'এই কী-তে ইতিমধ্যে একটি ফাইল আছে', localizationHeading: 'স্থানীয়করণ', localizationNote: diff --git a/src/shared/translations/locales/ca.ts b/src/shared/translations/locales/ca.ts index 3a6af26..cfbe6e4 100644 --- a/src/shared/translations/locales/ca.ts +++ b/src/shared/translations/locales/ca.ts @@ -89,12 +89,27 @@ export const ca: PluginDefaultTranslationsObject = { uploadPayloadField: 'Camps {{schema}} en format JSON. Exemple: `{"alt":"A caption"}`.', fileServe: 'El fitxer', filePartial: 'Una part del fitxer, per a una petició `Range`', + paramFileVersion: 'Retorna el fitxer desat amb aquest ID de versió.', uploadInstructionsSummary: 'Obté instruccions per pujar un fitxer abans de desar el document', uploadInstructionsResult: "On enviar els bytes del fitxer i el valor `file` que cal enviar amb la petició de creació o d'actualització", uploadStagePutSummary: 'Envia els bytes del fitxer per a una pujada temporal', uploadStageDeleteSummary: 'Elimina una pujada temporal', uploadStageResult: 'Fet, sense contingut', + paramPreferenceKey: 'La clau de la preferència, p. ex. `nav`.', + preferenceGet: "Obté una preferència de l'usuari actual", + preferenceGetResult: "La preferència desada, o `value: null` si no n'hi ha", + preferenceSet: "Desa una preferència de l'usuari actual", + preferenceSetBody: 'Envia el valor a `value`. Un cos sense `value` es desa sencer com a valor.', + preferenceSetResult: 'La preferència desada', + preferenceDelete: "Elimina una preferència de l'usuari actual", + preferenceDeleteResult: 'La preferència eliminada', + reorder: 'Mou documents en una llista ordenable', + reorderBody: + 'Mou `docsToMove` després de `target` amb `newKeyWillBe: "greater"`, o abans amb `"less"`. `orderableFieldName` és `_order` per a una col·lecció ordenable, o el camp d\'ordre d\'un join ordenable. Sense `target.key`, Payload només assigna claus d\'ordre als documents que no en tenen.', + reorderResult: + 'Les noves claus d\'ordre, o `message: "initial migration"` quan Payload va assignar les claus d\'ordre per primer cop', + errorReorder400: 'Cos de la sol·licitud no vàlid; el motiu és a `error`', error400: 'Error de validació o de consulta (ValidationError, QueryError)', error401: 'No autenticat (AuthenticationError)', @@ -118,6 +133,73 @@ export const ca: PluginDefaultTranslationsObject = { tagJobs: 'Tasques', tagUploads: 'Pujades', tagAccess: 'Accés', + tagReorder: 'Reordenació', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endpoints afegits pels plugins oficials de Payload.', + errorPlugin400: 'Sol·licitud no vàlida', + errorPlugin401: 'No autenticat', + errorPlugin403: 'Prohibit', + errorPlugin404: 'No trobat', + errorPlugin500: 'Error intern del servidor', + ecommerceAddItem: 'Afegeix un article a la cistella', + ecommerceRemoveItem: 'Treu un article de la cistella', + ecommerceUpdateItem: 'Canvia la quantitat d’un article de la cistella', + ecommerceClearCart: 'Treu tots els articles de la cistella', + ecommerceMergeCart: 'Fusiona una cistella de convidat amb aquesta cistella', + ecommerceCartAccess: + 'Permès per al propietari de la cistella, o per a una cistella de convidat amb el seu `secret` al cos.', + ecommerceCartResult: 'La cistella actualitzada', + errorPluginEcommerce404: 'Cistella no trobada o no accessible', + ecommerceQuantity: 'Nova quantitat, o `{ "$inc": n }` per canviar-la en n.', + ecommerceInitiatePayment: 'Inicia un pagament amb `{{method}}`', + ecommerceConfirmOrder: 'Confirma el pagament amb `{{method}}` i crea la comanda', + ecommercePaymentBody: + 'Fa servir `cartID` (amb `secret` per a una cistella de convidat) o la cistella de l’usuari. Sense usuari, `customerEmail` és obligatori. L’adaptador de pagament pot necessitar més camps.', + ecommerceInitiateResult: 'Pagament iniciat. L’adaptador afegeix els seus propis camps, p. ex. un secret de client.', + ecommerceConfirmResult: 'S’ha creat la comanda', + stripeWebhook: 'Rep esdeveniments de webhook de Stripe', + stripeWebhookBody: + 'L’esdeveniment de Stripe en brut, signat a la capçalera `Stripe-Signature`. El crida Stripe, no els clients de l’API.', + stripeWebhookResult: 'Esdeveniment rebut', + errorPluginStripeWebhook400: 'La comprovació de la signatura ha fallat', + stripeRest: 'Crida un mètode permès de l’API de Stripe', + stripeRestResult: 'El resultat de l’API de Stripe', + errorPluginStripeRest404: 'L’API de Stripe ha retornat un error', + mcp: 'Envia un missatge MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol sobre Streamable HTTP, amb respostes JSON. Les sol·licituds anònimes funcionen; les eines llistades depenen dels permisos de l’usuari. Els clients amb una versió del protocol de 2025 han d’enviar `Accept: application/json, text/event-stream`.', + mcpResult: 'Resposta JSON-RPC', + mcpOverrideAccess: 'Omet les comprovacions d’accés. Només per a desenvolupament.', + mcpGet: 'No compatible: el servidor no obre cap flux d’esdeveniments', + errorPluginMcpGet405: 'Mètode no permès, fes servir POST', + mcpProtocolVersion: 'Versió negociada del protocol MCP, p. ex. `2025-06-18`.', + mcpResult202: 'Acceptat: el cos només contenia notificacions o respostes', + errorPluginMcp404: 'Mètode MCP desconegut', + errorPluginMcp406: 'La capçalera `Accept` no inclou `application/json` o `text/event-stream`', + errorPluginMcp413: 'El cos de la sol·licitud és massa gran', + errorPluginMcp415: '`Content-Type` ha de ser `application/json`', + seoTitle: 'Genera el metatítol', + seoDescription: 'Genera la metadescripció', + seoUrl: 'Genera l’URL de previsualització', + seoImage: 'Genera la metaimatge', + seoBody: + 'El document en edició: `collectionSlug` o `globalSlug`, el seu `id` i les dades actuals de `doc`. Es passa a la teva funció de generació.', + seoResult: 'El valor generat. Una cadena buida si no hi ha cap funció de generació.', + searchReindex: 'Reconstrueix l’índex de cerca d’algunes col·leccions', + searchReindexResult: 'Resum de la reindexació', + tenantOptions: 'Llista els inquilins que l’usuari pot triar', + tenantOptionsResult: 'Opcions d’inquilí', + exportDownload: 'Exporta documents a un fitxer', + exportDownloadResult: 'El fitxer exportat', + exportPreview: 'Previsualitza una exportació', + importPreview: 'Previsualitza un fitxer d’importació', + previewResult: 'Una pàgina de documents de previsualització', + importFileData: 'Contingut del fitxer, codificat en base64.', + r2Upload: 'Puja un fitxer a R2 per parts', + r2UploadDesc: + 'Tres passos en una sola ruta. Inici: envia `collection`, `fileName` i `fileType`. Cada part: afegeix `multipartId`, `multipartKey`, `multipartNumber` i `signedReceipt`, i envia els bytes. Final: el mateix sense `multipartNumber`, amb la llista JSON de parts.', + r2UploadResult: 'Pujada iniciada, part pujada o pujada completada (la clau de l’objecte com a text)', + errorStorageR2412: 'Ja existeix un fitxer amb aquesta clau', localizationHeading: 'Localització', localizationNote: diff --git a/src/shared/translations/locales/cs.ts b/src/shared/translations/locales/cs.ts index 702655e..1be0a4c 100644 --- a/src/shared/translations/locales/cs.ts +++ b/src/shared/translations/locales/cs.ts @@ -87,11 +87,25 @@ export const cs: PluginDefaultTranslationsObject = { uploadPayloadField: 'Pole {{schema}} serializovaná do JSON. Příklad: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Soubor', filePartial: 'Část souboru pro požadavek `Range`', + paramFileVersion: 'Vrátí soubor uložený s tímto ID verze.', uploadInstructionsSummary: 'Získat pokyny k nahrání souboru před uložením dokumentu', uploadInstructionsResult: 'Kam poslat bajty souboru a hodnota `file` pro požadavek na vytvoření nebo úpravu', uploadStagePutSummary: 'Odeslat bajty souboru pro dočasné nahrání', uploadStageDeleteSummary: 'Smazat dočasné nahrání', uploadStageResult: 'Hotovo, bez obsahu', + paramPreferenceKey: 'Klíč předvolby, např. `nav`.', + preferenceGet: 'Získat předvolbu aktuálního uživatele', + preferenceGetResult: 'Uložená předvolba, nebo `value: null`, pokud neexistuje', + preferenceSet: 'Uložit předvolbu aktuálního uživatele', + preferenceSetBody: 'Pošlete hodnotu v `value`. Tělo bez `value` se uloží celé jako hodnota.', + preferenceSetResult: 'Uložená předvolba', + preferenceDelete: 'Smazat předvolbu aktuálního uživatele', + preferenceDeleteResult: 'Smazaná předvolba', + reorder: 'Přesunout dokumenty v seřaditelném seznamu', + reorderBody: + 'Přesune `docsToMove` za `target` s `newKeyWillBe: "greater"`, nebo před něj s `"less"`. `orderableFieldName` je `_order` u seřaditelné kolekce, nebo pole pořadí seřaditelného joinu. Bez `target.key` Payload jen přidělí klíče pořadí dokumentům, které je nemají.', + reorderResult: 'Nové klíče pořadí, nebo `message: "initial migration"`, když Payload poprvé nastavil klíče pořadí', + errorReorder400: 'Neplatné tělo požadavku; důvod je v `error`', error400: 'Chyba validace nebo dotazu (ValidationError, QueryError)', error401: 'Neautentizováno (AuthenticationError)', @@ -115,6 +129,72 @@ export const cs: PluginDefaultTranslationsObject = { tagJobs: 'Úlohy', tagUploads: 'Nahrávání', tagAccess: 'Přístup', + tagReorder: 'Pořadí', + tagPlugins: 'Pluginy', + tagPluginsDesc: 'Endpointy přidané oficiálními pluginy Payloadu.', + errorPlugin400: 'Neplatný požadavek', + errorPlugin401: 'Nepřihlášen', + errorPlugin403: 'Zakázáno', + errorPlugin404: 'Nenalezeno', + errorPlugin500: 'Interní chyba serveru', + ecommerceAddItem: 'Přidat položku do košíku', + ecommerceRemoveItem: 'Odebrat položku z košíku', + ecommerceUpdateItem: 'Změnit množství položky v košíku', + ecommerceClearCart: 'Odebrat všechny položky z košíku', + ecommerceMergeCart: 'Sloučit košík hosta s tímto košíkem', + ecommerceCartAccess: 'Povoleno vlastníkovi košíku, nebo pro košík hosta s jeho `secret` v těle požadavku.', + ecommerceCartResult: 'Aktualizovaný košík', + errorPluginEcommerce404: 'Košík nenalezen nebo není přístupný', + ecommerceQuantity: 'Nové množství, nebo `{ "$inc": n }` pro změnu o n.', + ecommerceInitiatePayment: 'Zahájit platbu přes `{{method}}`', + ecommerceConfirmOrder: 'Potvrdit platbu přes `{{method}}` a vytvořit objednávku', + ecommercePaymentBody: + 'Použije `cartID` (se `secret` pro košík hosta) nebo košík uživatele. Bez uživatele je `customerEmail` povinný. Platební adaptér může vyžadovat další pole.', + ecommerceInitiateResult: 'Platba zahájena. Adaptér přidává vlastní pole, např. client secret.', + ecommerceConfirmResult: 'Objednávka byla vytvořena', + stripeWebhook: 'Přijímat události webhooku Stripe', + stripeWebhookBody: + 'Nezpracovaná událost Stripe podepsaná v hlavičce `Stripe-Signature`. Volá ji Stripe, ne klienti API.', + stripeWebhookResult: 'Událost přijata', + errorPluginStripeWebhook400: 'Ověření podpisu selhalo', + stripeRest: 'Zavolat povolenou metodu Stripe API', + stripeRestResult: 'Výsledek Stripe API', + errorPluginStripeRest404: 'Stripe API vrátilo chybu', + mcp: 'Odeslat zprávu MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol přes Streamable HTTP s odpověďmi v JSON. Anonymní požadavky fungují; nabízené nástroje závisí na oprávněních uživatele. Klienti s verzí protokolu z roku 2025 musí posílat `Accept: application/json, text/event-stream`.', + mcpResult: 'Odpověď JSON-RPC', + mcpOverrideAccess: 'Přeskočit kontroly přístupu. Pouze pro vývoj.', + mcpGet: 'Nepodporováno: server neotevírá žádný stream událostí', + errorPluginMcpGet405: 'Metoda není povolena, použijte POST', + mcpProtocolVersion: 'Dohodnutá verze protokolu MCP, např. `2025-06-18`.', + mcpResult202: 'Přijato: tělo obsahovalo jen notifikace nebo odpovědi', + errorPluginMcp404: 'Neznámá metoda MCP', + errorPluginMcp406: 'Hlavička `Accept` neobsahuje `application/json` nebo `text/event-stream`', + errorPluginMcp413: 'Tělo požadavku je příliš velké', + errorPluginMcp415: '`Content-Type` musí být `application/json`', + seoTitle: 'Vygenerovat meta titulek', + seoDescription: 'Vygenerovat meta popis', + seoUrl: 'Vygenerovat URL náhledu', + seoImage: 'Vygenerovat meta obrázek', + seoBody: + 'Upravovaný dokument: `collectionSlug` nebo `globalSlug`, jeho `id` a aktuální data `doc`. Předává se vaší generovací funkci.', + seoResult: 'Vygenerovaná hodnota. Prázdný řetězec, pokud není nastavena generovací funkce.', + searchReindex: 'Znovu sestavit vyhledávací index pro vybrané kolekce', + searchReindexResult: 'Souhrn přeindexování', + tenantOptions: 'Vypsat tenanty, které si uživatel může vybrat', + tenantOptionsResult: 'Možnosti tenantů', + exportDownload: 'Exportovat dokumenty do souboru', + exportDownloadResult: 'Exportovaný soubor', + exportPreview: 'Náhled exportu', + importPreview: 'Náhled importního souboru', + previewResult: 'Stránka dokumentů náhledu', + importFileData: 'Obsah souboru kódovaný v base64.', + r2Upload: 'Nahrát soubor do R2 po částech', + r2UploadDesc: + 'Tři kroky na jedné cestě. Začátek: pošlete `collection`, `fileName` a `fileType`. Každá část: přidejte `multipartId`, `multipartKey`, `multipartNumber` a `signedReceipt` a pošlete bajty. Dokončení: totéž bez `multipartNumber`, se seznamem částí v JSON.', + r2UploadResult: 'Nahrávání zahájeno, část nahrána nebo nahrávání dokončeno (klíč objektu jako text)', + errorStorageR2412: 'Pod tímto klíčem už soubor existuje', localizationHeading: 'Lokalizace', localizationNote: diff --git a/src/shared/translations/locales/da.ts b/src/shared/translations/locales/da.ts index 85c9a43..630d998 100644 --- a/src/shared/translations/locales/da.ts +++ b/src/shared/translations/locales/da.ts @@ -88,12 +88,27 @@ export const da: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-strengkodede {{schema}}-felter. Eksempel: `{"alt":"A caption"}`.', fileServe: 'Filen', filePartial: 'En del af filen ved en `Range`-anmodning', + paramFileVersion: 'Returnerer filen, der er gemt med dette versions-ID.', uploadInstructionsSummary: 'Hent instruktioner til at uploade en fil, før dokumentet gemmes', uploadInstructionsResult: 'Hvor filens bytes skal sendes hen, og den `file`-værdi, der skal sendes med opret- eller opdater-anmodningen', uploadStagePutSummary: 'Send filens bytes til en midlertidig upload', uploadStageDeleteSummary: 'Slet en midlertidig upload', uploadStageResult: 'Udført, intet indhold', + paramPreferenceKey: 'Præferencens nøgle, f.eks. `nav`.', + preferenceGet: 'Hent en præference for den aktuelle bruger', + preferenceGetResult: 'Den gemte præference, eller `value: null` hvis der ikke er nogen', + preferenceSet: 'Gem en præference for den aktuelle bruger', + preferenceSetBody: 'Send værdien som `value`. En body uden `value` gemmes som selve værdien.', + preferenceSetResult: 'Den gemte præference', + preferenceDelete: 'Slet en præference for den aktuelle bruger', + preferenceDeleteResult: 'Den slettede præference', + reorder: 'Flyt dokumenter i en sorterbar liste', + reorderBody: + 'Flytter `docsToMove` efter `target` med `newKeyWillBe: "greater"`, eller før med `"less"`. `orderableFieldName` er `_order` for en sorterbar collection, eller rækkefølgefeltet for en sorterbar join. Uden `target.key` giver Payload kun rækkefølgenøgler til dokumenter, der ikke har nogen.', + reorderResult: + 'De nye rækkefølgenøgler, eller `message: "initial migration"` når Payload første gang satte rækkefølgenøglerne', + errorReorder400: 'Ugyldig request-body; årsagen står i `error`', error400: 'Validerings- eller forespørgselsfejl (ValidationError, QueryError)', error401: 'Ikke godkendt (AuthenticationError)', @@ -117,6 +132,72 @@ export const da: PluginDefaultTranslationsObject = { tagJobs: 'Jobs', tagUploads: 'Uploads', tagAccess: 'Adgang', + tagReorder: 'Rækkefølge', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endepunkter tilføjet af officielle Payload-plugins.', + errorPlugin400: 'Ugyldig anmodning', + errorPlugin401: 'Ikke godkendt', + errorPlugin403: 'Forbudt', + errorPlugin404: 'Ikke fundet', + errorPlugin500: 'Intern serverfejl', + ecommerceAddItem: 'Læg en vare i kurven', + ecommerceRemoveItem: 'Fjern en vare fra kurven', + ecommerceUpdateItem: 'Ændr antallet af en vare i kurven', + ecommerceClearCart: 'Fjern alle varer fra kurven', + ecommerceMergeCart: 'Flet en gæstekurv ind i denne kurv', + ecommerceCartAccess: 'Tilladt for kurvens ejer eller for en gæstekurv med dens `secret` i body.', + ecommerceCartResult: 'Den opdaterede kurv', + errorPluginEcommerce404: 'Kurven blev ikke fundet eller er ikke tilgængelig', + ecommerceQuantity: 'Nyt antal, eller `{ "$inc": n }` for at ændre det med n.', + ecommerceInitiatePayment: 'Start en betaling med `{{method}}`', + ecommerceConfirmOrder: 'Bekræft betalingen med `{{method}}` og opret ordren', + ecommercePaymentBody: + 'Bruger `cartID` (med `secret` for en gæstekurv) eller brugerens kurv. Uden bruger er `customerEmail` påkrævet. Betalingsadapteren kan kræve flere felter.', + ecommerceInitiateResult: 'Betaling startet. Adapteren tilføjer sine egne felter, fx en client secret.', + ecommerceConfirmResult: 'Ordren blev oprettet', + stripeWebhook: 'Modtag Stripe-webhookhændelser', + stripeWebhookBody: + 'Den rå Stripe-hændelse, signeret i headeren `Stripe-Signature`. Kaldes af Stripe, ikke af API-klienter.', + stripeWebhookResult: 'Hændelse modtaget', + errorPluginStripeWebhook400: 'Signaturkontrollen mislykkedes', + stripeRest: 'Kald en tilladt Stripe API-metode', + stripeRestResult: 'Resultatet fra Stripe API', + errorPluginStripeRest404: 'Stripe API returnerede en fejl', + mcp: 'Send en MCP JSON-RPC-besked', + mcpDesc: + 'Model Context Protocol over Streamable HTTP med JSON-svar. Anonyme anmodninger virker; de viste værktøjer afhænger af brugerens adgang. Klienter med en protokolversion fra 2025 skal sende `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC-svar', + mcpOverrideAccess: 'Spring adgangskontrol over. Kun til udvikling.', + mcpGet: 'Ikke understøttet: serveren åbner ingen hændelsesstrøm', + errorPluginMcpGet405: 'Metode ikke tilladt, brug POST', + mcpProtocolVersion: 'Den aftalte MCP-protokolversion, fx `2025-06-18`.', + mcpResult202: 'Accepteret: body indeholdt kun notifikationer eller svar', + errorPluginMcp404: 'Ukendt MCP-metode', + errorPluginMcp406: 'Headeren `Accept` mangler `application/json` eller `text/event-stream`', + errorPluginMcp413: 'Anmodningens body er for stor', + errorPluginMcp415: '`Content-Type` skal være `application/json`', + seoTitle: 'Generér metatitlen', + seoDescription: 'Generér metabeskrivelsen', + seoUrl: 'Generér preview-URL’en', + seoImage: 'Generér metabilledet', + seoBody: + 'Dokumentet, der redigeres: `collectionSlug` eller `globalSlug`, dets `id` og de aktuelle `doc`-data. Sendes videre til din generate-funktion.', + seoResult: 'Den genererede værdi. En tom streng, når ingen generate-funktion er sat.', + searchReindex: 'Genopbyg søgeindekset for udvalgte samlinger', + searchReindexResult: 'Opsummering af genindeksering', + tenantOptions: 'Vis de tenants, brugeren kan vælge', + tenantOptionsResult: 'Tenant-muligheder', + exportDownload: 'Eksportér dokumenter til en fil', + exportDownloadResult: 'Eksportfilen', + exportPreview: 'Forhåndsvis en eksport', + importPreview: 'Forhåndsvis en importfil', + previewResult: 'En side med forhåndsvisningsdokumenter', + importFileData: 'Filindhold, base64-kodet.', + r2Upload: 'Upload en fil til R2 i dele', + r2UploadDesc: + 'Tre trin på én rute. Start: send `collection`, `fileName` og `fileType`. Hver del: tilføj `multipartId`, `multipartKey`, `multipartNumber` og `signedReceipt`, og send bytes. Afslut: det samme uden `multipartNumber`, med JSON-listen over dele.', + r2UploadResult: 'Upload startet, del uploadet eller upload fuldført (objektnøglen som tekst)', + errorStorageR2412: 'Der findes allerede en fil med denne nøgle', localizationHeading: 'Lokalisering', localizationNote: diff --git a/src/shared/translations/locales/de.ts b/src/shared/translations/locales/de.ts index 98a2c0e..9398eb2 100644 --- a/src/shared/translations/locales/de.ts +++ b/src/shared/translations/locales/de.ts @@ -90,12 +90,27 @@ export const de: PluginDefaultTranslationsObject = { uploadPayloadField: 'Als JSON-String serialisierte {{schema}}-Felder. Beispiel: `{"alt":"A caption"}`.', fileServe: 'Die Datei', filePartial: 'Ein Teil der Datei bei einer `Range`-Anfrage', + paramFileVersion: 'Liefert die Datei, die mit dieser Versions-ID gespeichert wurde.', uploadInstructionsSummary: 'Anweisungen zum Hochladen einer Datei vor dem Speichern des Dokuments abrufen', uploadInstructionsResult: 'Wohin die Bytes der Datei gesendet werden und welcher `file`-Wert mit der Erstellungs- oder Aktualisierungsanfrage gesendet wird', uploadStagePutSummary: 'Die Bytes der Datei für einen Zwischen-Upload senden', uploadStageDeleteSummary: 'Einen Zwischen-Upload löschen', uploadStageResult: 'Erledigt, kein Inhalt', + paramPreferenceKey: 'Der Schlüssel der Einstellung, z. B. `nav`.', + preferenceGet: 'Eine Einstellung des aktuellen Benutzers abrufen', + preferenceGetResult: 'Die gespeicherte Einstellung oder `value: null`, wenn keine vorhanden ist', + preferenceSet: 'Eine Einstellung des aktuellen Benutzers speichern', + preferenceSetBody: 'Senden Sie den Wert als `value`. Ein Body ohne `value` wird selbst als Wert gespeichert.', + preferenceSetResult: 'Die gespeicherte Einstellung', + preferenceDelete: 'Eine Einstellung des aktuellen Benutzers löschen', + preferenceDeleteResult: 'Die gelöschte Einstellung', + reorder: 'Dokumente in einer sortierbaren Liste verschieben', + reorderBody: + 'Verschiebt `docsToMove` mit `newKeyWillBe: "greater"` hinter `target` oder mit `"less"` davor. `orderableFieldName` ist `_order` für eine sortierbare Collection oder das Sortierfeld eines sortierbaren Joins. Ohne `target.key` vergibt Payload nur Sortierschlüssel an Dokumente, die noch keinen haben.', + reorderResult: + 'Die neuen Sortierschlüssel oder `message: "initial migration"`, wenn Payload die Sortierschlüssel erstmals gesetzt hat', + errorReorder400: 'Ungültiger Request-Body; der Grund steht in `error`', error400: 'Validierungs- oder Abfragefehler (ValidationError, QueryError)', error401: 'Nicht authentifiziert (AuthenticationError)', @@ -119,6 +134,73 @@ export const de: PluginDefaultTranslationsObject = { tagJobs: 'Jobs', tagUploads: 'Uploads', tagAccess: 'Zugriff', + tagReorder: 'Sortierung', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endpunkte offizieller Payload-Plugins.', + errorPlugin400: 'Ungültige Anfrage', + errorPlugin401: 'Nicht angemeldet', + errorPlugin403: 'Zugriff verweigert', + errorPlugin404: 'Nicht gefunden', + errorPlugin500: 'Interner Serverfehler', + ecommerceAddItem: 'Artikel zum Warenkorb hinzufügen', + ecommerceRemoveItem: 'Artikel aus dem Warenkorb entfernen', + ecommerceUpdateItem: 'Menge eines Warenkorbartikels ändern', + ecommerceClearCart: 'Alle Artikel aus dem Warenkorb entfernen', + ecommerceMergeCart: 'Gast-Warenkorb in diesen Warenkorb übernehmen', + ecommerceCartAccess: + 'Erlaubt für den Besitzer des Warenkorbs oder für einen Gast-Warenkorb mit seinem `secret` im Body.', + ecommerceCartResult: 'Der aktualisierte Warenkorb', + errorPluginEcommerce404: 'Warenkorb nicht gefunden oder nicht zugänglich', + ecommerceQuantity: 'Neue Menge, oder `{ "$inc": n }`, um sie um n zu ändern.', + ecommerceInitiatePayment: 'Zahlung mit `{{method}}` starten', + ecommerceConfirmOrder: 'Zahlung mit `{{method}}` bestätigen und Bestellung anlegen', + ecommercePaymentBody: + 'Nutzt `cartID` (mit `secret` bei einem Gast-Warenkorb) oder den Warenkorb des Benutzers. Ohne Benutzer ist `customerEmail` Pflicht. Der Zahlungsadapter kann weitere Felder benötigen.', + ecommerceInitiateResult: 'Zahlung gestartet. Der Adapter ergänzt eigene Felder, z. B. ein Client-Secret.', + ecommerceConfirmResult: 'Die Bestellung wurde angelegt', + stripeWebhook: 'Stripe-Webhook-Ereignisse empfangen', + stripeWebhookBody: + 'Das rohe Stripe-Ereignis, signiert im Header `Stripe-Signature`. Wird von Stripe aufgerufen, nicht von API-Clients.', + stripeWebhookResult: 'Ereignis empfangen', + errorPluginStripeWebhook400: 'Die Signaturprüfung ist fehlgeschlagen', + stripeRest: 'Eine erlaubte Stripe-API-Methode aufrufen', + stripeRestResult: 'Das Ergebnis der Stripe-API', + errorPluginStripeRest404: 'Die Stripe-API hat einen Fehler gemeldet', + mcp: 'Eine MCP-JSON-RPC-Nachricht senden', + mcpDesc: + 'Model Context Protocol über Streamable HTTP mit JSON-Antworten. Anonyme Anfragen funktionieren; welche Tools aufgelistet werden, hängt von den Rechten des Benutzers ab. Clients mit einer Protokollversion von 2025 müssen `Accept: application/json, text/event-stream` senden.', + mcpResult: 'JSON-RPC-Antwort', + mcpOverrideAccess: 'Zugriffsprüfungen überspringen. Nur für die Entwicklung.', + mcpGet: 'Nicht unterstützt: Der Server öffnet keinen Event-Stream', + errorPluginMcpGet405: 'Methode nicht erlaubt, POST verwenden', + mcpProtocolVersion: 'Ausgehandelte MCP-Protokollversion, z. B. `2025-06-18`.', + mcpResult202: 'Angenommen: Der Body enthielt nur Benachrichtigungen oder Antworten', + errorPluginMcp404: 'Unbekannte MCP-Methode', + errorPluginMcp406: 'Dem Header `Accept` fehlt `application/json` oder `text/event-stream`', + errorPluginMcp413: 'Der Request-Body ist zu groß', + errorPluginMcp415: '`Content-Type` muss `application/json` sein', + seoTitle: 'Meta-Titel generieren', + seoDescription: 'Meta-Beschreibung generieren', + seoUrl: 'Vorschau-URL generieren', + seoImage: 'Meta-Bild generieren', + seoBody: + 'Das bearbeitete Dokument: `collectionSlug` oder `globalSlug`, seine `id` und die aktuellen `doc`-Daten. Wird an Ihre Generate-Funktion übergeben.', + seoResult: 'Der generierte Wert. Ein leerer String, wenn keine Generate-Funktion gesetzt ist.', + searchReindex: 'Suchindex für einzelne Collections neu aufbauen', + searchReindexResult: 'Zusammenfassung der Neuindexierung', + tenantOptions: 'Mandanten auflisten, die der Benutzer wählen kann', + tenantOptionsResult: 'Mandantenoptionen', + exportDownload: 'Dokumente in eine Datei exportieren', + exportDownloadResult: 'Die Exportdatei', + exportPreview: 'Vorschau eines Exports', + importPreview: 'Vorschau einer Importdatei', + previewResult: 'Eine Seite mit Vorschau-Dokumenten', + importFileData: 'Dateiinhalt, Base64-kodiert.', + r2Upload: 'Datei in Teilen zu R2 hochladen', + r2UploadDesc: + 'Drei Schritte auf einer Route. Start: `collection`, `fileName` und `fileType` senden. Jeden Teil hochladen: `multipartId`, `multipartKey`, `multipartNumber` und `signedReceipt` ergänzen und die Bytes senden. Abschließen: dasselbe ohne `multipartNumber`, mit der JSON-Liste der Teile.', + r2UploadResult: 'Upload gestartet, Teil hochgeladen oder Upload abgeschlossen (der Objektschlüssel als Text)', + errorStorageR2412: 'Unter diesem Schlüssel existiert bereits eine Datei', localizationHeading: 'Lokalisierung', localizationNote: diff --git a/src/shared/translations/locales/en.ts b/src/shared/translations/locales/en.ts index 2e62ad5..8b54c68 100644 --- a/src/shared/translations/locales/en.ts +++ b/src/shared/translations/locales/en.ts @@ -86,12 +86,26 @@ export const en: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-stringified {{schema}} fields. Example: `{"alt":"A caption"}`.', fileServe: 'The file', filePartial: 'Part of the file, for a `Range` request', + paramFileVersion: 'Return the file stored with this version ID.', uploadInstructionsSummary: 'Get instructions to upload a file before saving the document', uploadInstructionsResult: 'Where to send the file bytes, and the `file` value to send with the create or update request', uploadStagePutSummary: 'Send the file bytes for a staged upload', uploadStageDeleteSummary: 'Delete a staged upload', uploadStageResult: 'Done, no content', + paramPreferenceKey: 'The preference key, e.g. `nav`.', + preferenceGet: 'Get a preference of the current user', + preferenceGetResult: 'The saved preference, or `value: null` when there is none', + preferenceSet: 'Save a preference of the current user', + preferenceSetBody: 'Send the value as `value`. A body without `value` is saved as the value.', + preferenceSetResult: 'The saved preference', + preferenceDelete: 'Delete a preference of the current user', + preferenceDeleteResult: 'The deleted preference', + reorder: 'Move documents in an orderable list', + reorderBody: + 'Moves `docsToMove` after `target` with `newKeyWillBe: "greater"`, or before it with `"less"`. `orderableFieldName` is `_order` for an orderable collection, or the order field of an orderable join. Without `target.key`, Payload only gives order keys to documents that have none.', + reorderResult: 'The new order keys, or `message: "initial migration"` when Payload first set the order keys', + errorReorder400: 'Invalid request body; the reason is in `error`', error400: 'Validation or query error (ValidationError, QueryError)', error401: 'Not authenticated (AuthenticationError)', @@ -114,6 +128,72 @@ export const en: PluginDefaultTranslationsObject = { tagJobs: 'Jobs', tagUploads: 'Uploads', tagAccess: 'Access', + tagReorder: 'Reorder', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endpoints added by official Payload plugins.', + errorPlugin400: 'Invalid request', + errorPlugin401: 'Not authenticated', + errorPlugin403: 'Forbidden', + errorPlugin404: 'Not found', + errorPlugin500: 'Internal server error', + ecommerceAddItem: 'Add an item to the cart', + ecommerceRemoveItem: 'Remove an item from the cart', + ecommerceUpdateItem: 'Change the quantity of a cart item', + ecommerceClearCart: 'Remove every item from the cart', + ecommerceMergeCart: 'Merge a guest cart into this cart', + ecommerceCartAccess: 'Allowed for the cart owner, or for a guest cart with its `secret` in the body.', + ecommerceCartResult: 'The updated cart', + errorPluginEcommerce404: 'Cart not found or not accessible', + ecommerceQuantity: 'New quantity, or `{ "$inc": n }` to change it by n.', + ecommerceInitiatePayment: 'Start a payment with `{{method}}`', + ecommerceConfirmOrder: 'Confirm the payment with `{{method}}` and create the order', + ecommercePaymentBody: + 'Uses `cartID` (with `secret` for a guest cart) or the user’s cart. Without a user, `customerEmail` is required. The payment adapter may need more fields.', + ecommerceInitiateResult: 'Payment started. The adapter adds its own fields, e.g. a client secret.', + ecommerceConfirmResult: 'The order was created', + stripeWebhook: 'Receive Stripe webhook events', + stripeWebhookBody: + 'The raw Stripe event, signed in the `Stripe-Signature` header. Called by Stripe, not by API clients.', + stripeWebhookResult: 'Event received', + errorPluginStripeWebhook400: 'The signature check failed', + stripeRest: 'Call an allowed Stripe API method', + stripeRestResult: 'The Stripe API result', + errorPluginStripeRest404: 'The Stripe API returned an error', + mcp: 'Send an MCP JSON-RPC message', + mcpDesc: + 'Model Context Protocol over Streamable HTTP, with JSON responses. Anonymous requests work; the tools listed depend on the user’s access. Clients on a 2025 protocol version must send `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC response', + mcpOverrideAccess: 'Skip access checks. Development only.', + mcpGet: 'Not supported: the server opens no event stream', + errorPluginMcpGet405: 'Method not allowed, use POST', + mcpProtocolVersion: 'Negotiated MCP protocol version, e.g. `2025-06-18`.', + mcpResult202: 'Accepted: the body held only notifications or responses', + errorPluginMcp404: 'Unknown MCP method', + errorPluginMcp406: 'The `Accept` header lacks `application/json` or `text/event-stream`', + errorPluginMcp413: 'The request body is too large', + errorPluginMcp415: '`Content-Type` must be `application/json`', + seoTitle: 'Generate the meta title', + seoDescription: 'Generate the meta description', + seoUrl: 'Generate the preview URL', + seoImage: 'Generate the meta image', + seoBody: + 'The document being edited: `collectionSlug` or `globalSlug`, its `id`, and the current `doc` data. Passed on to your generate function.', + seoResult: 'The generated value. An empty string when no generate function is set.', + searchReindex: 'Rebuild the search index for some collections', + searchReindexResult: 'Reindex summary', + tenantOptions: 'List the tenants the user can pick', + tenantOptionsResult: 'Tenant options', + exportDownload: 'Export documents to a file', + exportDownloadResult: 'The export file', + exportPreview: 'Preview an export', + importPreview: 'Preview an import file', + previewResult: 'A page of preview documents', + importFileData: 'File contents, base64-encoded.', + r2Upload: 'Upload a file to R2 in parts', + r2UploadDesc: + 'Three steps on one route. Start: send `collection`, `fileName` and `fileType`. Upload each part: add `multipartId`, `multipartKey`, `multipartNumber` and `signedReceipt`, and send the bytes. Complete: the same without `multipartNumber`, with the JSON list of parts.', + r2UploadResult: 'Upload started, part uploaded, or upload completed (the object key as text)', + errorStorageR2412: 'A file already exists at this key', localizationHeading: 'Localization', localizationNote: diff --git a/src/shared/translations/locales/es.ts b/src/shared/translations/locales/es.ts index 04f2174..ea83a5c 100644 --- a/src/shared/translations/locales/es.ts +++ b/src/shared/translations/locales/es.ts @@ -88,12 +88,27 @@ export const es: PluginDefaultTranslationsObject = { uploadPayloadField: 'Campos {{schema}} serializados como JSON. Ejemplo: `{"alt":"A caption"}`.', fileServe: 'El archivo', filePartial: 'Una parte del archivo, para una petición `Range`', + paramFileVersion: 'Devuelve el archivo guardado con este ID de versión.', uploadInstructionsSummary: 'Obtener instrucciones para subir un archivo antes de guardar el documento', uploadInstructionsResult: 'Dónde enviar los bytes del archivo y el valor `file` que se envía con la petición de creación o actualización', uploadStagePutSummary: 'Enviar los bytes del archivo para una subida temporal', uploadStageDeleteSummary: 'Eliminar una subida temporal', uploadStageResult: 'Hecho, sin contenido', + paramPreferenceKey: 'La clave de la preferencia, p. ej. `nav`.', + preferenceGet: 'Obtener una preferencia del usuario actual', + preferenceGetResult: 'La preferencia guardada, o `value: null` si no existe', + preferenceSet: 'Guardar una preferencia del usuario actual', + preferenceSetBody: 'Envía el valor en `value`. Un cuerpo sin `value` se guarda tal cual como valor.', + preferenceSetResult: 'La preferencia guardada', + preferenceDelete: 'Eliminar una preferencia del usuario actual', + preferenceDeleteResult: 'La preferencia eliminada', + reorder: 'Mover documentos en una lista ordenable', + reorderBody: + 'Mueve `docsToMove` después de `target` con `newKeyWillBe: "greater"`, o antes con `"less"`. `orderableFieldName` es `_order` para una colección ordenable, o el campo de orden de un join ordenable. Sin `target.key`, Payload solo asigna claves de orden a los documentos que no tienen.', + reorderResult: + 'Las nuevas claves de orden, o `message: "initial migration"` cuando Payload asignó las claves de orden por primera vez', + errorReorder400: 'Cuerpo de la solicitud no válido; el motivo está en `error`', error400: 'Error de validación o de consulta (ValidationError, QueryError)', error401: 'No autenticado (AuthenticationError)', @@ -117,6 +132,73 @@ export const es: PluginDefaultTranslationsObject = { tagJobs: 'Trabajos', tagUploads: 'Subidas', tagAccess: 'Acceso', + tagReorder: 'Reordenar', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endpoints añadidos por los plugins oficiales de Payload.', + errorPlugin400: 'Solicitud no válida', + errorPlugin401: 'No autenticado', + errorPlugin403: 'Prohibido', + errorPlugin404: 'No encontrado', + errorPlugin500: 'Error interno del servidor', + ecommerceAddItem: 'Añadir un artículo al carrito', + ecommerceRemoveItem: 'Quitar un artículo del carrito', + ecommerceUpdateItem: 'Cambiar la cantidad de un artículo del carrito', + ecommerceClearCart: 'Quitar todos los artículos del carrito', + ecommerceMergeCart: 'Fusionar un carrito de invitado en este carrito', + ecommerceCartAccess: + 'Permitido para el dueño del carrito, o para un carrito de invitado con su `secret` en el cuerpo.', + ecommerceCartResult: 'El carrito actualizado', + errorPluginEcommerce404: 'Carrito no encontrado o no accesible', + ecommerceQuantity: 'Nueva cantidad, o `{ "$inc": n }` para cambiarla en n.', + ecommerceInitiatePayment: 'Iniciar un pago con `{{method}}`', + ecommerceConfirmOrder: 'Confirmar el pago con `{{method}}` y crear el pedido', + ecommercePaymentBody: + 'Usa `cartID` (con `secret` para un carrito de invitado) o el carrito del usuario. Sin usuario, `customerEmail` es obligatorio. El adaptador de pago puede requerir más campos.', + ecommerceInitiateResult: 'Pago iniciado. El adaptador añade sus propios campos, p. ej. un secreto de cliente.', + ecommerceConfirmResult: 'Se creó el pedido', + stripeWebhook: 'Recibir eventos de webhook de Stripe', + stripeWebhookBody: + 'El evento de Stripe sin procesar, firmado en la cabecera `Stripe-Signature`. Lo llama Stripe, no los clientes de la API.', + stripeWebhookResult: 'Evento recibido', + errorPluginStripeWebhook400: 'La verificación de la firma falló', + stripeRest: 'Llamar a un método permitido de la API de Stripe', + stripeRestResult: 'El resultado de la API de Stripe', + errorPluginStripeRest404: 'La API de Stripe devolvió un error', + mcp: 'Enviar un mensaje MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol sobre Streamable HTTP, con respuestas JSON. Las solicitudes anónimas funcionan; las herramientas listadas dependen de los permisos del usuario. Los clientes con una versión del protocolo de 2025 deben enviar `Accept: application/json, text/event-stream`.', + mcpResult: 'Respuesta JSON-RPC', + mcpOverrideAccess: 'Omitir las comprobaciones de acceso. Solo para desarrollo.', + mcpGet: 'No compatible: el servidor no abre ningún flujo de eventos', + errorPluginMcpGet405: 'Método no permitido, use POST', + mcpProtocolVersion: 'Versión negociada del protocolo MCP, p. ej. `2025-06-18`.', + mcpResult202: 'Aceptado: el cuerpo solo contenía notificaciones o respuestas', + errorPluginMcp404: 'Método MCP desconocido', + errorPluginMcp406: 'A la cabecera `Accept` le falta `application/json` o `text/event-stream`', + errorPluginMcp413: 'El cuerpo de la solicitud es demasiado grande', + errorPluginMcp415: '`Content-Type` debe ser `application/json`', + seoTitle: 'Generar el meta título', + seoDescription: 'Generar la meta descripción', + seoUrl: 'Generar la URL de vista previa', + seoImage: 'Generar la meta imagen', + seoBody: + 'El documento en edición: `collectionSlug` o `globalSlug`, su `id` y los datos actuales de `doc`. Se pasa a tu función de generación.', + seoResult: 'El valor generado. Una cadena vacía si no hay función de generación.', + searchReindex: 'Reconstruir el índice de búsqueda de algunas colecciones', + searchReindexResult: 'Resumen de la reindexación', + tenantOptions: 'Listar los inquilinos que el usuario puede elegir', + tenantOptionsResult: 'Opciones de inquilino', + exportDownload: 'Exportar documentos a un archivo', + exportDownloadResult: 'El archivo exportado', + exportPreview: 'Vista previa de una exportación', + importPreview: 'Vista previa de un archivo de importación', + previewResult: 'Una página de documentos de vista previa', + importFileData: 'Contenido del archivo, codificado en base64.', + r2Upload: 'Subir un archivo a R2 por partes', + r2UploadDesc: + 'Tres pasos en una sola ruta. Inicio: envía `collection`, `fileName` y `fileType`. Cada parte: añade `multipartId`, `multipartKey`, `multipartNumber` y `signedReceipt`, y envía los bytes. Final: lo mismo sin `multipartNumber`, con la lista JSON de partes.', + r2UploadResult: 'Subida iniciada, parte subida o subida completada (la clave del objeto como texto)', + errorStorageR2412: 'Ya existe un archivo con esta clave', localizationHeading: 'Localización', localizationNote: diff --git a/src/shared/translations/locales/et.ts b/src/shared/translations/locales/et.ts index 174de0e..c673d11 100644 --- a/src/shared/translations/locales/et.ts +++ b/src/shared/translations/locales/et.ts @@ -89,12 +89,27 @@ export const et: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-stringitud {{schema}} väljad. Näide: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Fail', filePartial: 'Osa failist `Range` päringu jaoks', + paramFileVersion: 'Tagastab selle versiooni ID-ga salvestatud faili.', uploadInstructionsSummary: 'Hangi juhised faili üleslaadimiseks enne dokumendi salvestamist', uploadInstructionsResult: 'Kuhu saata faili baidid ja milline `file` väärtus saata loomise või uuendamise päringuga', uploadStagePutSummary: 'Saada faili baidid ajutise üleslaadimise jaoks', uploadStageDeleteSummary: 'Kustuta ajutine üleslaadimine', uploadStageResult: 'Valmis, sisu puudub', + paramPreferenceKey: 'Eelistuse võti, nt `nav`.', + preferenceGet: 'Hangi praeguse kasutaja eelistus', + preferenceGetResult: 'Salvestatud eelistus või `value: null`, kui seda pole', + preferenceSet: 'Salvesta praeguse kasutaja eelistus', + preferenceSetBody: 'Saada väärtus väljal `value`. Ilma `value`-ta keha salvestatakse tervikuna väärtusena.', + preferenceSetResult: 'Salvestatud eelistus', + preferenceDelete: 'Kustuta praeguse kasutaja eelistus', + preferenceDeleteResult: 'Kustutatud eelistus', + reorder: 'Liiguta dokumente järjestatavas loendis', + reorderBody: + 'Liigutab `docsToMove` `newKeyWillBe: "greater"` korral `target` järele või `"less"` korral selle ette. `orderableFieldName` on järjestatava kollektsiooni puhul `_order` või järjestatava joini järjestusväli. Ilma `target.key`-ta annab Payload järjestusvõtmed ainult dokumentidele, millel neid pole.', + reorderResult: + 'Uued järjestusvõtmed või `message: "initial migration"`, kui Payload määras järjestusvõtmed esimest korda', + errorReorder400: 'Vigane päringu keha; põhjus on väljal `error`', error400: 'Valideerimis- või päringuviga (ValidationError, QueryError)', error401: 'Autentimata (AuthenticationError)', @@ -118,6 +133,72 @@ export const et: PluginDefaultTranslationsObject = { tagJobs: 'Tööd', tagUploads: 'Üleslaadimised', tagAccess: 'Juurdepääs', + tagReorder: 'Järjestus', + tagPlugins: 'Pluginad', + tagPluginsDesc: 'Payloadi ametlike pluginate lisatud lõpp-punktid.', + errorPlugin400: 'Vigane päring', + errorPlugin401: 'Pole autenditud', + errorPlugin403: 'Keelatud', + errorPlugin404: 'Ei leitud', + errorPlugin500: 'Serveri sisemine viga', + ecommerceAddItem: 'Lisa toode ostukorvi', + ecommerceRemoveItem: 'Eemalda toode ostukorvist', + ecommerceUpdateItem: 'Muuda ostukorvi toote kogust', + ecommerceClearCart: 'Eemalda ostukorvist kõik tooted', + ecommerceMergeCart: 'Ühenda külalise ostukorv selle ostukorviga', + ecommerceCartAccess: 'Lubatud ostukorvi omanikule või külalise ostukorvile, mille `secret` on päringu kehas.', + ecommerceCartResult: 'Uuendatud ostukorv', + errorPluginEcommerce404: 'Ostukorvi ei leitud või see pole ligipääsetav', + ecommerceQuantity: 'Uus kogus või `{ "$inc": n }`, et muuta seda n võrra.', + ecommerceInitiatePayment: 'Alusta makset meetodiga `{{method}}`', + ecommerceConfirmOrder: 'Kinnita makse meetodiga `{{method}}` ja loo tellimus', + ecommercePaymentBody: + 'Kasutab `cartID` (külalise ostukorvi puhul koos `secret`) või kasutaja ostukorvi. Ilma kasutajata on `customerEmail` kohustuslik. Makseadapter võib vajada lisavälju.', + ecommerceInitiateResult: 'Makse alustatud. Adapter lisab oma väljad, nt client secret.', + ecommerceConfirmResult: 'Tellimus loodi', + stripeWebhook: 'Võta vastu Stripe’i webhooki sündmusi', + stripeWebhookBody: + 'Töötlemata Stripe’i sündmus, allkirjastatud päises `Stripe-Signature`. Seda kutsub Stripe, mitte API kliendid.', + stripeWebhookResult: 'Sündmus vastu võetud', + errorPluginStripeWebhook400: 'Allkirja kontroll ebaõnnestus', + stripeRest: 'Kutsu välja lubatud Stripe API meetod', + stripeRestResult: 'Stripe API tulemus', + errorPluginStripeRest404: 'Stripe API tagastas vea', + mcp: 'Saada MCP JSON-RPC sõnum', + mcpDesc: + 'Model Context Protocol üle Streamable HTTP, JSON-vastustega. Anonüümsed päringud töötavad; loetletud tööriistad sõltuvad kasutaja õigustest. 2025. aasta protokolliversiooniga kliendid peavad saatma `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC vastus', + mcpOverrideAccess: 'Jäta ligipääsukontrollid vahele. Ainult arenduseks.', + mcpGet: 'Ei toetata: server ei ava sündmuste voogu', + errorPluginMcpGet405: 'Meetod pole lubatud, kasuta POST-i', + mcpProtocolVersion: 'Kokkulepitud MCP protokolli versioon, nt `2025-06-18`.', + mcpResult202: 'Vastu võetud: kehas olid ainult teavitused või vastused', + errorPluginMcp404: 'Tundmatu MCP meetod', + errorPluginMcp406: 'Päises `Accept` puudub `application/json` või `text/event-stream`', + errorPluginMcp413: 'Päringu keha on liiga suur', + errorPluginMcp415: '`Content-Type` peab olema `application/json`', + seoTitle: 'Genereeri metapealkiri', + seoDescription: 'Genereeri metakirjeldus', + seoUrl: 'Genereeri eelvaate URL', + seoImage: 'Genereeri metapilt', + seoBody: + 'Muudetav dokument: `collectionSlug` või `globalSlug`, selle `id` ja praegused `doc` andmed. Antakse edasi sinu genereerimisfunktsioonile.', + seoResult: 'Genereeritud väärtus. Tühi string, kui genereerimisfunktsiooni pole määratud.', + searchReindex: 'Ehita mõne kollektsiooni otsinguindeks uuesti üles', + searchReindexResult: 'Ümberindekseerimise kokkuvõte', + tenantOptions: 'Loetle rentnikud, keda kasutaja saab valida', + tenantOptionsResult: 'Rentnike valikud', + exportDownload: 'Ekspordi dokumendid faili', + exportDownloadResult: 'Ekspordifail', + exportPreview: 'Ekspordi eelvaade', + importPreview: 'Impordifaili eelvaade', + previewResult: 'Lehekülg eelvaate dokumente', + importFileData: 'Faili sisu, base64-kodeeritud.', + r2Upload: 'Laadi fail R2-te osade kaupa üles', + r2UploadDesc: + 'Kolm sammu ühel marsruudil. Algus: saada `collection`, `fileName` ja `fileType`. Iga osa: lisa `multipartId`, `multipartKey`, `multipartNumber` ja `signedReceipt` ning saada baidid. Lõpp: sama ilma `multipartNumber`-ita, koos osade JSON-loendiga.', + r2UploadResult: 'Üleslaadimine alustatud, osa üles laaditud või üleslaadimine lõpetatud (objekti võti tekstina)', + errorStorageR2412: 'Selle võtmega fail on juba olemas', localizationHeading: 'Lokaliseerimine', localizationNote: diff --git a/src/shared/translations/locales/fa.ts b/src/shared/translations/locales/fa.ts index 09c3397..a61031a 100644 --- a/src/shared/translations/locales/fa.ts +++ b/src/shared/translations/locales/fa.ts @@ -90,11 +90,26 @@ export const fa: PluginDefaultTranslationsObject = { uploadPayloadField: 'فیلدهای {{schema}} به‌صورت JSON رشته‌شده. مثال: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'فایل', filePartial: 'بخشی از فایل، برای درخواست `Range`', + paramFileVersion: 'فایل ذخیره‌شده با این شناسه نسخه را برمی‌گرداند.', uploadInstructionsSummary: 'دریافت دستورالعمل بارگذاری فایل پیش از ذخیره سند', uploadInstructionsResult: 'مقصد ارسال بایت‌های فایل و مقدار `file` که همراه درخواست ایجاد یا به‌روزرسانی ارسال می‌شود', uploadStagePutSummary: 'ارسال بایت‌های فایل برای بارگذاری موقت', uploadStageDeleteSummary: 'حذف بارگذاری موقت', uploadStageResult: 'انجام شد، بدون محتوا', + paramPreferenceKey: 'کلید ترجیح، مثلاً `nav`.', + preferenceGet: 'دریافت یک ترجیح کاربر فعلی', + preferenceGetResult: 'ترجیح ذخیره‌شده، یا `value: null` اگر وجود نداشته باشد', + preferenceSet: 'ذخیره یک ترجیح کاربر فعلی', + preferenceSetBody: 'مقدار را در `value` بفرستید. بدنه‌ای بدون `value` به‌طور کامل به‌عنوان مقدار ذخیره می‌شود.', + preferenceSetResult: 'ترجیح ذخیره‌شده', + preferenceDelete: 'حذف یک ترجیح کاربر فعلی', + preferenceDeleteResult: 'ترجیح حذف‌شده', + reorder: 'جابه‌جایی اسناد در یک فهرست قابل مرتب‌سازی', + reorderBody: + '`docsToMove` را با `newKeyWillBe: "greater"` به بعد از `target` و با `"less"` به قبل از آن منتقل می‌کند. `orderableFieldName` برای مجموعه قابل مرتب‌سازی `_order` است، یا فیلد ترتیب یک join قابل مرتب‌سازی. بدون `target.key`، Payload فقط به اسنادی که کلید ترتیب ندارند کلید ترتیب می‌دهد.', + reorderResult: + 'کلیدهای ترتیب جدید، یا `message: "initial migration"` وقتی Payload برای اولین بار کلیدهای ترتیب را تعیین کرد', + errorReorder400: 'بدنه درخواست نامعتبر است؛ دلیل در `error` آمده است', error400: 'خطای اعتبارسنجی یا پرس‌وجو (ValidationError, QueryError)', error401: 'احراز هویت نشده (AuthenticationError)', @@ -118,6 +133,72 @@ export const fa: PluginDefaultTranslationsObject = { tagJobs: 'کارها', tagUploads: 'بارگذاری‌ها', tagAccess: 'دسترسی', + tagReorder: 'ترتیب‌دهی', + tagPlugins: 'افزونه‌ها', + tagPluginsDesc: 'نقاط پایانی که افزونه‌های رسمی Payload اضافه می‌کنند.', + errorPlugin400: 'درخواست نامعتبر', + errorPlugin401: 'احراز هویت نشده', + errorPlugin403: 'ممنوع', + errorPlugin404: 'یافت نشد', + errorPlugin500: 'خطای داخلی سرور', + ecommerceAddItem: 'افزودن کالا به سبد', + ecommerceRemoveItem: 'حذف کالا از سبد', + ecommerceUpdateItem: 'تغییر تعداد یک کالای سبد', + ecommerceClearCart: 'حذف همه کالاها از سبد', + ecommerceMergeCart: 'ادغام سبد مهمان با این سبد', + ecommerceCartAccess: 'مجاز برای مالک سبد، یا برای سبد مهمان با `secret` آن در بدنه درخواست.', + ecommerceCartResult: 'سبد به‌روزشده', + errorPluginEcommerce404: 'سبد یافت نشد یا در دسترس نیست', + ecommerceQuantity: 'تعداد جدید، یا `{ "$inc": n }` برای تغییر آن به اندازه n.', + ecommerceInitiatePayment: 'شروع پرداخت با `{{method}}`', + ecommerceConfirmOrder: 'تأیید پرداخت با `{{method}}` و ایجاد سفارش', + ecommercePaymentBody: + 'از `cartID` (با `secret` برای سبد مهمان) یا سبد کاربر استفاده می‌کند. بدون کاربر، `customerEmail` الزامی است. آداپتور پرداخت ممکن است فیلدهای بیشتری بخواهد.', + ecommerceInitiateResult: 'پرداخت شروع شد. آداپتور فیلدهای خودش را اضافه می‌کند، مثلاً client secret.', + ecommerceConfirmResult: 'سفارش ایجاد شد', + stripeWebhook: 'دریافت رویدادهای webhook از Stripe', + stripeWebhookBody: + 'رویداد خام Stripe که در هدر `Stripe-Signature` امضا شده است. Stripe آن را فرا می‌خواند، نه کلاینت‌های API.', + stripeWebhookResult: 'رویداد دریافت شد', + errorPluginStripeWebhook400: 'بررسی امضا ناموفق بود', + stripeRest: 'فراخوانی یک متد مجاز از API استرایپ', + stripeRestResult: 'نتیجه API استرایپ', + errorPluginStripeRest404: 'API استرایپ خطا برگرداند', + mcp: 'ارسال پیام MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol روی Streamable HTTP با پاسخ‌های JSON. درخواست‌های ناشناس کار می‌کنند؛ ابزارهای فهرست‌شده به دسترسی کاربر بستگی دارند. کلاینت‌هایی که از نسخه ۲۰۲۵ پروتکل استفاده می‌کنند باید `Accept: application/json, text/event-stream` بفرستند.', + mcpResult: 'پاسخ JSON-RPC', + mcpOverrideAccess: 'رد شدن از بررسی‌های دسترسی. فقط برای توسعه.', + mcpGet: 'پشتیبانی نمی‌شود: سرور هیچ جریان رویدادی باز نمی‌کند', + errorPluginMcpGet405: 'متد مجاز نیست، از POST استفاده کنید', + mcpProtocolVersion: 'نسخه توافق‌شده پروتکل MCP، مثلاً `2025-06-18`.', + mcpResult202: 'پذیرفته شد: بدنه فقط شامل اعلان‌ها یا پاسخ‌ها بود', + errorPluginMcp404: 'متد MCP ناشناخته', + errorPluginMcp406: 'هدر `Accept` شامل `application/json` یا `text/event-stream` نیست', + errorPluginMcp413: 'بدنه درخواست بیش از حد بزرگ است', + errorPluginMcp415: '`Content-Type` باید `application/json` باشد', + seoTitle: 'تولید عنوان متا', + seoDescription: 'تولید توضیحات متا', + seoUrl: 'تولید URL پیش‌نمایش', + seoImage: 'تولید تصویر متا', + seoBody: + 'سندی که در حال ویرایش است: `collectionSlug` یا `globalSlug`، `id` آن و داده‌های فعلی `doc`. به تابع تولید شما داده می‌شود.', + seoResult: 'مقدار تولیدشده. اگر تابع تولیدی تنظیم نشده باشد، رشته خالی.', + searchReindex: 'بازسازی نمایه جستجو برای برخی مجموعه‌ها', + searchReindexResult: 'خلاصه نمایه‌سازی مجدد', + tenantOptions: 'فهرست مستأجرانی که کاربر می‌تواند انتخاب کند', + tenantOptionsResult: 'گزینه‌های مستأجر', + exportDownload: 'خروجی گرفتن از اسناد در یک فایل', + exportDownloadResult: 'فایل خروجی', + exportPreview: 'پیش‌نمایش خروجی', + importPreview: 'پیش‌نمایش فایل ورودی', + previewResult: 'یک صفحه از اسناد پیش‌نمایش', + importFileData: 'محتوای فایل، با کدگذاری base64.', + r2Upload: 'بارگذاری فایل در R2 به‌صورت چندبخشی', + r2UploadDesc: + 'سه مرحله روی یک مسیر. شروع: `collection`، `fileName` و `fileType` را بفرستید. هر بخش: `multipartId`، `multipartKey`، `multipartNumber` و `signedReceipt` را اضافه کنید و بایت‌ها را بفرستید. پایان: همان، بدون `multipartNumber`، با فهرست JSON بخش‌ها.', + r2UploadResult: 'بارگذاری شروع شد، بخش بارگذاری شد یا بارگذاری کامل شد (کلید شیء به‌صورت متن)', + errorStorageR2412: 'فایلی با این کلید از قبل وجود دارد', localizationHeading: 'بومی‌سازی', localizationNote: diff --git a/src/shared/translations/locales/fr.ts b/src/shared/translations/locales/fr.ts index dcb3524..5c7996e 100644 --- a/src/shared/translations/locales/fr.ts +++ b/src/shared/translations/locales/fr.ts @@ -89,12 +89,27 @@ export const fr: PluginDefaultTranslationsObject = { uploadPayloadField: 'Champs {{schema}} au format JSON. Exemple : `{"alt":"A caption"}`.', fileServe: 'Le fichier', filePartial: 'Une partie du fichier, pour une requête `Range`', + paramFileVersion: 'Renvoie le fichier enregistré avec cet ID de version.', uploadInstructionsSummary: "Obtenir les instructions pour téléverser un fichier avant d'enregistrer le document", uploadInstructionsResult: 'Où envoyer les octets du fichier, et la valeur `file` à envoyer avec la requête de création ou de mise à jour', uploadStagePutSummary: 'Envoyer les octets du fichier pour un téléversement temporaire', uploadStageDeleteSummary: 'Supprimer un téléversement temporaire', uploadStageResult: 'Terminé, aucun contenu', + paramPreferenceKey: 'La clé de la préférence, par ex. `nav`.', + preferenceGet: "Obtenir une préférence de l'utilisateur actuel", + preferenceGetResult: "La préférence enregistrée, ou `value: null` s'il n'y en a pas", + preferenceSet: "Enregistrer une préférence de l'utilisateur actuel", + preferenceSetBody: 'Envoyez la valeur dans `value`. Un corps sans `value` est enregistré tel quel comme valeur.', + preferenceSetResult: 'La préférence enregistrée', + preferenceDelete: "Supprimer une préférence de l'utilisateur actuel", + preferenceDeleteResult: 'La préférence supprimée', + reorder: 'Déplacer des documents dans une liste ordonnable', + reorderBody: + 'Déplace `docsToMove` après `target` avec `newKeyWillBe: "greater"`, ou avant avec `"less"`. `orderableFieldName` vaut `_order` pour une collection ordonnable, ou le champ d\'ordre d\'une jointure ordonnable. Sans `target.key`, Payload attribue seulement des clés d\'ordre aux documents qui n\'en ont pas.', + reorderResult: + 'Les nouvelles clés d\'ordre, ou `message: "initial migration"` quand Payload a défini les clés d\'ordre pour la première fois', + errorReorder400: 'Corps de requête invalide, la raison figure dans `error`', error400: 'Erreur de validation ou de requête (ValidationError, QueryError)', error401: 'Non authentifié (AuthenticationError)', @@ -118,6 +133,73 @@ export const fr: PluginDefaultTranslationsObject = { tagJobs: 'Jobs', tagUploads: 'Téléversements', tagAccess: 'Accès', + tagReorder: 'Réorganisation', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Points de terminaison ajoutés par les plugins officiels de Payload.', + errorPlugin400: 'Requête invalide', + errorPlugin401: 'Non authentifié', + errorPlugin403: 'Accès refusé', + errorPlugin404: 'Introuvable', + errorPlugin500: 'Erreur interne du serveur', + ecommerceAddItem: 'Ajouter un article au panier', + ecommerceRemoveItem: 'Retirer un article du panier', + ecommerceUpdateItem: 'Modifier la quantité d’un article du panier', + ecommerceClearCart: 'Vider le panier', + ecommerceMergeCart: 'Fusionner un panier invité dans ce panier', + ecommerceCartAccess: + 'Autorisé pour le propriétaire du panier, ou pour un panier invité avec son `secret` dans le corps.', + ecommerceCartResult: 'Le panier mis à jour', + errorPluginEcommerce404: 'Panier introuvable ou inaccessible', + ecommerceQuantity: 'Nouvelle quantité, ou `{ "$inc": n }` pour la modifier de n.', + ecommerceInitiatePayment: 'Démarrer un paiement avec `{{method}}`', + ecommerceConfirmOrder: 'Confirmer le paiement avec `{{method}}` et créer la commande', + ecommercePaymentBody: + 'Utilise `cartID` (avec `secret` pour un panier invité) ou le panier de l’utilisateur. Sans utilisateur, `customerEmail` est obligatoire. L’adaptateur de paiement peut exiger d’autres champs.', + ecommerceInitiateResult: 'Paiement démarré. L’adaptateur ajoute ses propres champs, par ex. un secret client.', + ecommerceConfirmResult: 'La commande a été créée', + stripeWebhook: 'Recevoir les événements webhook de Stripe', + stripeWebhookBody: + 'L’événement Stripe brut, signé dans l’en-tête `Stripe-Signature`. Appelé par Stripe, pas par les clients de l’API.', + stripeWebhookResult: 'Événement reçu', + errorPluginStripeWebhook400: 'La vérification de la signature a échoué', + stripeRest: 'Appeler une méthode autorisée de l’API Stripe', + stripeRestResult: 'Le résultat de l’API Stripe', + errorPluginStripeRest404: 'L’API Stripe a renvoyé une erreur', + mcp: 'Envoyer un message MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol via Streamable HTTP, avec des réponses JSON. Les requêtes anonymes fonctionnent ; les outils listés dépendent des droits de l’utilisateur. Les clients utilisant une version du protocole de 2025 doivent envoyer `Accept: application/json, text/event-stream`.', + mcpResult: 'Réponse JSON-RPC', + mcpOverrideAccess: 'Ignorer les contrôles d’accès. Développement uniquement.', + mcpGet: 'Non pris en charge : le serveur n’ouvre aucun flux d’événements', + errorPluginMcpGet405: 'Méthode non autorisée, utilisez POST', + mcpProtocolVersion: 'Version négociée du protocole MCP, par ex. `2025-06-18`.', + mcpResult202: 'Accepté : le corps ne contenait que des notifications ou des réponses', + errorPluginMcp404: 'Méthode MCP inconnue', + errorPluginMcp406: 'L’en-tête `Accept` ne contient pas `application/json` ou `text/event-stream`', + errorPluginMcp413: 'Le corps de la requête est trop volumineux', + errorPluginMcp415: '`Content-Type` doit être `application/json`', + seoTitle: 'Générer le méta-titre', + seoDescription: 'Générer la méta-description', + seoUrl: 'Générer l’URL d’aperçu', + seoImage: 'Générer la méta-image', + seoBody: + 'Le document en cours d’édition : `collectionSlug` ou `globalSlug`, son `id` et les données `doc` actuelles. Transmis à votre fonction de génération.', + seoResult: 'La valeur générée. Une chaîne vide si aucune fonction de génération n’est définie.', + searchReindex: 'Reconstruire l’index de recherche pour certaines collections', + searchReindexResult: 'Résumé de la réindexation', + tenantOptions: 'Lister les locataires que l’utilisateur peut choisir', + tenantOptionsResult: 'Options de locataire', + exportDownload: 'Exporter des documents vers un fichier', + exportDownloadResult: 'Le fichier d’export', + exportPreview: 'Aperçu d’un export', + importPreview: 'Aperçu d’un fichier d’import', + previewResult: 'Une page de documents d’aperçu', + importFileData: 'Contenu du fichier, encodé en base64.', + r2Upload: 'Téléverser un fichier vers R2 en plusieurs parties', + r2UploadDesc: + 'Trois étapes sur une seule route. Début : envoyez `collection`, `fileName` et `fileType`. Chaque partie : ajoutez `multipartId`, `multipartKey`, `multipartNumber` et `signedReceipt`, puis envoyez les octets. Fin : la même chose sans `multipartNumber`, avec la liste JSON des parties.', + r2UploadResult: 'Téléversement démarré, partie envoyée ou téléversement terminé (la clé de l’objet en texte)', + errorStorageR2412: 'Un fichier existe déjà à cette clé', localizationHeading: 'Localisation', localizationNote: diff --git a/src/shared/translations/locales/he.ts b/src/shared/translations/locales/he.ts index 3aa58f8..84d4cee 100644 --- a/src/shared/translations/locales/he.ts +++ b/src/shared/translations/locales/he.ts @@ -84,11 +84,25 @@ export const he: PluginDefaultTranslationsObject = { uploadPayloadField: 'שדות {{schema}} מקודדים כ-JSON. דוגמה: `{"alt":"A caption"}`.', fileServe: 'הקובץ', filePartial: 'חלק מהקובץ, עבור בקשת `Range`', + paramFileVersion: 'מחזיר את הקובץ שנשמר עם מזהה הגרסה הזה.', uploadInstructionsSummary: 'קבלת הוראות להעלאת קובץ לפני שמירת המסמך', uploadInstructionsResult: 'לאן לשלוח את בתי הקובץ, וערך `file` לשליחה עם בקשת היצירה או העדכון', uploadStagePutSummary: 'שליחת בתי הקובץ להעלאה זמנית', uploadStageDeleteSummary: 'מחיקת העלאה זמנית', uploadStageResult: 'בוצע, ללא תוכן', + paramPreferenceKey: 'מפתח ההעדפה, למשל `nav`.', + preferenceGet: 'קבלת העדפה של המשתמש הנוכחי', + preferenceGetResult: 'ההעדפה השמורה, או `value: null` אם אין כזו', + preferenceSet: 'שמירת העדפה של המשתמש הנוכחי', + preferenceSetBody: 'שלח את הערך ב-`value`. גוף ללא `value` נשמר כולו כערך.', + preferenceSetResult: 'ההעדפה השמורה', + preferenceDelete: 'מחיקת העדפה של המשתמש הנוכחי', + preferenceDeleteResult: 'ההעדפה שנמחקה', + reorder: 'העברת מסמכים ברשימה הניתנת לסידור', + reorderBody: + 'מעביר את `docsToMove` אחרי `target` עם `newKeyWillBe: "greater"`, או לפניו עם `"less"`. `orderableFieldName` הוא `_order` לאוסף הניתן לסידור, או שדה הסדר של join הניתן לסידור. ללא `target.key`, Payload רק מקצה מפתחות סדר למסמכים שאין להם.', + reorderResult: 'מפתחות הסדר החדשים, או `message: "initial migration"` כאשר Payload הגדיר את מפתחות הסדר לראשונה', + errorReorder400: 'גוף בקשה לא תקין; הסיבה מופיעה ב-`error`', error400: 'שגיאת אימות או שאילתה (ValidationError, QueryError)', error401: 'לא מאומת (AuthenticationError)', @@ -111,6 +125,71 @@ export const he: PluginDefaultTranslationsObject = { tagJobs: 'משימות', tagUploads: 'העלאות', tagAccess: 'גישה', + tagReorder: 'סידור מחדש', + tagPlugins: 'תוספים', + tagPluginsDesc: 'נקודות קצה שמוסיפים התוספים הרשמיים של Payload.', + errorPlugin400: 'בקשה לא תקינה', + errorPlugin401: 'לא מאומת', + errorPlugin403: 'אסור', + errorPlugin404: 'לא נמצא', + errorPlugin500: 'שגיאת שרת פנימית', + ecommerceAddItem: 'הוספת פריט לעגלה', + ecommerceRemoveItem: 'הסרת פריט מהעגלה', + ecommerceUpdateItem: 'שינוי הכמות של פריט בעגלה', + ecommerceClearCart: 'הסרת כל הפריטים מהעגלה', + ecommerceMergeCart: 'מיזוג עגלת אורח לתוך עגלה זו', + ecommerceCartAccess: 'מותר לבעלי העגלה, או לעגלת אורח עם ה-`secret` שלה בגוף הבקשה.', + ecommerceCartResult: 'העגלה המעודכנת', + errorPluginEcommerce404: 'העגלה לא נמצאה או שאינה נגישה', + ecommerceQuantity: 'כמות חדשה, או `{ "$inc": n }` כדי לשנות אותה ב-n.', + ecommerceInitiatePayment: 'התחלת תשלום באמצעות `{{method}}`', + ecommerceConfirmOrder: 'אישור התשלום באמצעות `{{method}}` ויצירת ההזמנה', + ecommercePaymentBody: + 'משתמש ב-`cartID` (עם `secret` לעגלת אורח) או בעגלה של המשתמש. ללא משתמש, `customerEmail` נדרש. מתאם התשלום עשוי לדרוש שדות נוספים.', + ecommerceInitiateResult: 'התשלום התחיל. המתאם מוסיף שדות משלו, למשל client secret.', + ecommerceConfirmResult: 'ההזמנה נוצרה', + stripeWebhook: 'קבלת אירועי webhook מ-Stripe', + stripeWebhookBody: 'אירוע Stripe גולמי, חתום בכותרת `Stripe-Signature`. Stripe קורא לו, לא לקוחות API.', + stripeWebhookResult: 'האירוע התקבל', + errorPluginStripeWebhook400: 'בדיקת החתימה נכשלה', + stripeRest: 'קריאה למתודה מותרת של Stripe API', + stripeRestResult: 'התוצאה של Stripe API', + errorPluginStripeRest404: 'Stripe API החזיר שגיאה', + mcp: 'שליחת הודעת MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol מעל Streamable HTTP, עם תשובות JSON. בקשות אנונימיות עובדות; הכלים המוצגים תלויים בהרשאות המשתמש. לקוחות עם גרסת פרוטוקול מ-2025 חייבים לשלוח `Accept: application/json, text/event-stream`.', + mcpResult: 'תשובת JSON-RPC', + mcpOverrideAccess: 'דילוג על בדיקות הרשאה. לפיתוח בלבד.', + mcpGet: 'לא נתמך: השרת אינו פותח זרם אירועים', + errorPluginMcpGet405: 'המתודה אינה מותרת, יש להשתמש ב-POST', + mcpProtocolVersion: 'גרסת פרוטוקול MCP שסוכמה, למשל `2025-06-18`.', + mcpResult202: 'התקבל: הגוף הכיל רק התראות או תשובות', + errorPluginMcp404: 'מתודת MCP לא מוכרת', + errorPluginMcp406: 'בכותרת `Accept` חסר `application/json` או `text/event-stream`', + errorPluginMcp413: 'גוף הבקשה גדול מדי', + errorPluginMcp415: '`Content-Type` חייב להיות `application/json`', + seoTitle: 'יצירת כותרת המטא', + seoDescription: 'יצירת תיאור המטא', + seoUrl: 'יצירת כתובת URL לתצוגה מקדימה', + seoImage: 'יצירת תמונת המטא', + seoBody: + 'המסמך שנערך: `collectionSlug` או `globalSlug`, ה-`id` שלו ונתוני `doc` הנוכחיים. מועבר לפונקציית היצירה שלך.', + seoResult: 'הערך שנוצר. מחרוזת ריקה כשלא הוגדרה פונקציית יצירה.', + searchReindex: 'בנייה מחדש של אינדקס החיפוש עבור חלק מהאוספים', + searchReindexResult: 'סיכום האינדוקס מחדש', + tenantOptions: 'רשימת הדיירים שהמשתמש יכול לבחור', + tenantOptionsResult: 'אפשרויות דיירים', + exportDownload: 'ייצוא מסמכים לקובץ', + exportDownloadResult: 'קובץ הייצוא', + exportPreview: 'תצוגה מקדימה של ייצוא', + importPreview: 'תצוגה מקדימה של קובץ ייבוא', + previewResult: 'עמוד של מסמכי תצוגה מקדימה', + importFileData: 'תוכן הקובץ, בקידוד base64.', + r2Upload: 'העלאת קובץ ל-R2 בחלקים', + r2UploadDesc: + 'שלושה שלבים בנתיב אחד. התחלה: שלחו `collection`, `fileName` ו-`fileType`. כל חלק: הוסיפו `multipartId`, `multipartKey`, `multipartNumber` ו-`signedReceipt` ושלחו את הבתים. סיום: אותו דבר בלי `multipartNumber`, עם רשימת החלקים ב-JSON.', + r2UploadResult: 'ההעלאה התחילה, חלק הועלה או שההעלאה הושלמה (מפתח האובייקט כטקסט)', + errorStorageR2412: 'כבר קיים קובץ במפתח זה', localizationHeading: 'לוקליזציה', localizationNote: diff --git a/src/shared/translations/locales/hr.ts b/src/shared/translations/locales/hr.ts index 0092561..e1373f5 100644 --- a/src/shared/translations/locales/hr.ts +++ b/src/shared/translations/locales/hr.ts @@ -89,12 +89,27 @@ export const hr: PluginDefaultTranslationsObject = { uploadPayloadField: 'Polja {{schema}} pretvorena u JSON niz. Primjer: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Datoteka', filePartial: 'Dio datoteke za zahtjev `Range`', + paramFileVersion: 'Vraća datoteku spremljenu s ovim ID-om verzije.', uploadInstructionsSummary: 'Dohvati upute za prijenos datoteke prije spremanja dokumenta', uploadInstructionsResult: 'Kamo poslati bajtove datoteke i vrijednost `file` koja se šalje sa zahtjevom za stvaranje ili ažuriranje', uploadStagePutSummary: 'Pošalji bajtove datoteke za privremeni prijenos', uploadStageDeleteSummary: 'Izbriši privremeni prijenos', uploadStageResult: 'Gotovo, bez sadržaja', + paramPreferenceKey: 'Ključ postavke, npr. `nav`.', + preferenceGet: 'Dohvati postavku trenutnog korisnika', + preferenceGetResult: 'Spremljena postavka ili `value: null` ako je nema', + preferenceSet: 'Spremi postavku trenutnog korisnika', + preferenceSetBody: 'Pošaljite vrijednost u `value`. Tijelo bez `value` sprema se u cijelosti kao vrijednost.', + preferenceSetResult: 'Spremljena postavka', + preferenceDelete: 'Izbriši postavku trenutnog korisnika', + preferenceDeleteResult: 'Izbrisana postavka', + reorder: 'Premjesti dokumente na popisu s redoslijedom', + reorderBody: + 'Premješta `docsToMove` iza `target` uz `newKeyWillBe: "greater"` ili ispred njega uz `"less"`. `orderableFieldName` je `_order` za kolekciju s redoslijedom ili polje redoslijeda za join s redoslijedom. Bez `target.key` Payload samo dodjeljuje ključeve redoslijeda dokumentima koji ih nemaju.', + reorderResult: + 'Novi ključevi redoslijeda ili `message: "initial migration"` kada je Payload prvi put postavio ključeve redoslijeda', + errorReorder400: 'Nevažeće tijelo zahtjeva; razlog je u `error`', error400: 'Pogreška valjanosti ili upita (ValidationError, QueryError)', error401: 'Nije autenticirano (AuthenticationError)', @@ -118,6 +133,72 @@ export const hr: PluginDefaultTranslationsObject = { tagJobs: 'Poslovi', tagUploads: 'Prijenosi', tagAccess: 'Pristup', + tagReorder: 'Redoslijed', + tagPlugins: 'Dodaci', + tagPluginsDesc: 'Krajnje točke koje dodaju službeni Payload dodaci.', + errorPlugin400: 'Neispravan zahtjev', + errorPlugin401: 'Niste prijavljeni', + errorPlugin403: 'Zabranjeno', + errorPlugin404: 'Nije pronađeno', + errorPlugin500: 'Interna pogreška poslužitelja', + ecommerceAddItem: 'Dodaj artikl u košaricu', + ecommerceRemoveItem: 'Ukloni artikl iz košarice', + ecommerceUpdateItem: 'Promijeni količinu artikla u košarici', + ecommerceClearCart: 'Ukloni sve artikle iz košarice', + ecommerceMergeCart: 'Spoji košaricu gosta s ovom košaricom', + ecommerceCartAccess: 'Dopušteno vlasniku košarice ili za košaricu gosta s njezinim `secret` u tijelu zahtjeva.', + ecommerceCartResult: 'Ažurirana košarica', + errorPluginEcommerce404: 'Košarica nije pronađena ili nije dostupna', + ecommerceQuantity: 'Nova količina ili `{ "$inc": n }` za promjenu za n.', + ecommerceInitiatePayment: 'Pokreni plaćanje putem `{{method}}`', + ecommerceConfirmOrder: 'Potvrdi plaćanje putem `{{method}}` i izradi narudžbu', + ecommercePaymentBody: + 'Koristi `cartID` (sa `secret` za košaricu gosta) ili košaricu korisnika. Bez korisnika `customerEmail` je obavezan. Adapteru za plaćanje mogu trebati dodatna polja.', + ecommerceInitiateResult: 'Plaćanje je pokrenuto. Adapter dodaje vlastita polja, npr. client secret.', + ecommerceConfirmResult: 'Narudžba je izrađena', + stripeWebhook: 'Primi Stripe webhook događaje', + stripeWebhookBody: + 'Neobrađeni Stripe događaj, potpisan u zaglavlju `Stripe-Signature`. Poziva ga Stripe, ne API klijenti.', + stripeWebhookResult: 'Događaj primljen', + errorPluginStripeWebhook400: 'Provjera potpisa nije uspjela', + stripeRest: 'Pozovi dopuštenu metodu Stripe API-ja', + stripeRestResult: 'Rezultat Stripe API-ja', + errorPluginStripeRest404: 'Stripe API vratio je pogrešku', + mcp: 'Pošalji MCP JSON-RPC poruku', + mcpDesc: + 'Model Context Protocol preko Streamable HTTP-a, s JSON odgovorima. Anonimni zahtjevi rade; navedeni alati ovise o pravima korisnika. Klijenti s verzijom protokola iz 2025. moraju slati `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC odgovor', + mcpOverrideAccess: 'Preskoči provjere pristupa. Samo za razvoj.', + mcpGet: 'Nije podržano: poslužitelj ne otvara tok događaja', + errorPluginMcpGet405: 'Metoda nije dopuštena, koristite POST', + mcpProtocolVersion: 'Dogovorena verzija MCP protokola, npr. `2025-06-18`.', + mcpResult202: 'Prihvaćeno: tijelo je sadržavalo samo obavijesti ili odgovore', + errorPluginMcp404: 'Nepoznata MCP metoda', + errorPluginMcp406: 'Zaglavlju `Accept` nedostaje `application/json` ili `text/event-stream`', + errorPluginMcp413: 'Tijelo zahtjeva je preveliko', + errorPluginMcp415: '`Content-Type` mora biti `application/json`', + seoTitle: 'Generiraj meta naslov', + seoDescription: 'Generiraj meta opis', + seoUrl: 'Generiraj URL za pregled', + seoImage: 'Generiraj meta sliku', + seoBody: + 'Dokument koji se uređuje: `collectionSlug` ili `globalSlug`, njegov `id` i trenutačni podaci `doc`. Prosljeđuje se vašoj funkciji za generiranje.', + seoResult: 'Generirana vrijednost. Prazan niz ako funkcija za generiranje nije postavljena.', + searchReindex: 'Ponovno izgradi indeks pretraživanja za neke kolekcije', + searchReindexResult: 'Sažetak ponovnog indeksiranja', + tenantOptions: 'Popis zakupaca koje korisnik može odabrati', + tenantOptionsResult: 'Opcije zakupaca', + exportDownload: 'Izvezi dokumente u datoteku', + exportDownloadResult: 'Datoteka izvoza', + exportPreview: 'Pregled izvoza', + importPreview: 'Pregled datoteke za uvoz', + previewResult: 'Stranica dokumenata za pregled', + importFileData: 'Sadržaj datoteke, kodiran u base64.', + r2Upload: 'Prenesi datoteku u R2 u dijelovima', + r2UploadDesc: + 'Tri koraka na jednoj ruti. Početak: pošaljite `collection`, `fileName` i `fileType`. Svaki dio: dodajte `multipartId`, `multipartKey`, `multipartNumber` i `signedReceipt` i pošaljite bajtove. Završetak: isto bez `multipartNumber`, s JSON popisom dijelova.', + r2UploadResult: 'Prijenos započet, dio prenesen ili prijenos završen (ključ objekta kao tekst)', + errorStorageR2412: 'Datoteka s ovim ključem već postoji', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/shared/translations/locales/hu.ts b/src/shared/translations/locales/hu.ts index 849e76f..0f5155f 100644 --- a/src/shared/translations/locales/hu.ts +++ b/src/shared/translations/locales/hu.ts @@ -87,12 +87,28 @@ export const hu: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-stringgé alakított {{schema}} mezők. Példa: `{"alt":"A caption"}`.', fileServe: 'A fájl', filePartial: 'A fájl egy része `Range` kéréshez', + paramFileVersion: 'Az ezzel a verzióazonosítóval mentett fájlt adja vissza.', uploadInstructionsSummary: 'Utasítások lekérése fájl feltöltéséhez a dokumentum mentése előtt', uploadInstructionsResult: 'Hová kell küldeni a fájl bájtjait, és milyen `file` értéket kell küldeni a létrehozási vagy frissítési kéréssel', uploadStagePutSummary: 'A fájl bájtjainak küldése ideiglenes feltöltéshez', uploadStageDeleteSummary: 'Ideiglenes feltöltés törlése', uploadStageResult: 'Kész, nincs tartalom', + paramPreferenceKey: 'A beállítás kulcsa, pl. `nav`.', + preferenceGet: 'Az aktuális felhasználó egy beállításának lekérése', + preferenceGetResult: 'A mentett beállítás, vagy `value: null`, ha nincs ilyen', + preferenceSet: 'Az aktuális felhasználó egy beállításának mentése', + preferenceSetBody: + 'Küldje az értéket a `value` mezőben. A `value` nélküli törzs teljes egészében értékként mentődik.', + preferenceSetResult: 'A mentett beállítás', + preferenceDelete: 'Az aktuális felhasználó egy beállításának törlése', + preferenceDeleteResult: 'A törölt beállítás', + reorder: 'Dokumentumok áthelyezése rendezhető listában', + reorderBody: + 'A `docsToMove` dokumentumokat `newKeyWillBe: "greater"` esetén a `target` mögé, `"less"` esetén elé helyezi. Az `orderableFieldName` rendezhető gyűjteménynél `_order`, rendezhető joinnál annak sorrendmezője. `target.key` nélkül a Payload csak a sorrendkulccsal nem rendelkező dokumentumoknak ad sorrendkulcsot.', + reorderResult: + 'Az új sorrendkulcsok, vagy `message: "initial migration"`, amikor a Payload először állította be a sorrendkulcsokat', + errorReorder400: 'Érvénytelen kéréstörzs; az ok az `error` mezőben van', error400: 'Érvényesítési vagy lekérdezési hiba (ValidationError, QueryError)', error401: 'Nincs hitelesítve (AuthenticationError)', @@ -116,6 +132,73 @@ export const hu: PluginDefaultTranslationsObject = { tagJobs: 'Feladatok', tagUploads: 'Feltöltések', tagAccess: 'Hozzáférés', + tagReorder: 'Sorrend', + tagPlugins: 'Bővítmények', + tagPluginsDesc: 'A hivatalos Payload-bővítmények által hozzáadott végpontok.', + errorPlugin400: 'Érvénytelen kérés', + errorPlugin401: 'Nincs hitelesítve', + errorPlugin403: 'Tiltott', + errorPlugin404: 'Nem található', + errorPlugin500: 'Belső szerverhiba', + ecommerceAddItem: 'Termék hozzáadása a kosárhoz', + ecommerceRemoveItem: 'Termék eltávolítása a kosárból', + ecommerceUpdateItem: 'Kosártétel mennyiségének módosítása', + ecommerceClearCart: 'Minden termék eltávolítása a kosárból', + ecommerceMergeCart: 'Vendégkosár egyesítése ezzel a kosárral', + ecommerceCartAccess: + 'A kosár tulajdonosának engedélyezett, vagy vendégkosárnál a kérés törzsében lévő `secret` értékkel.', + ecommerceCartResult: 'A frissített kosár', + errorPluginEcommerce404: 'A kosár nem található vagy nem elérhető', + ecommerceQuantity: 'Új mennyiség, vagy `{ "$inc": n }` az n-nel való módosításhoz.', + ecommerceInitiatePayment: 'Fizetés indítása ezzel: `{{method}}`', + ecommerceConfirmOrder: 'Fizetés megerősítése ezzel: `{{method}}`, és a rendelés létrehozása', + ecommercePaymentBody: + 'A `cartID` értéket (vendégkosárnál `secret` mellett) vagy a felhasználó kosarát használja. Felhasználó nélkül a `customerEmail` kötelező. A fizetési adapter további mezőket kérhet.', + ecommerceInitiateResult: 'A fizetés elindult. Az adapter saját mezőket ad hozzá, pl. client secretet.', + ecommerceConfirmResult: 'A rendelés létrejött', + stripeWebhook: 'Stripe webhook-események fogadása', + stripeWebhookBody: + 'A nyers Stripe-esemény, a `Stripe-Signature` fejlécben aláírva. A Stripe hívja, nem az API-kliensek.', + stripeWebhookResult: 'Esemény fogadva', + errorPluginStripeWebhook400: 'Az aláírás ellenőrzése sikertelen', + stripeRest: 'Engedélyezett Stripe API-metódus hívása', + stripeRestResult: 'A Stripe API eredménye', + errorPluginStripeRest404: 'A Stripe API hibát adott vissza', + mcp: 'MCP JSON-RPC üzenet küldése', + mcpDesc: + 'Model Context Protocol Streamable HTTP-n, JSON-válaszokkal. A névtelen kérések működnek; a felsorolt eszközök a felhasználó jogosultságaitól függnek. A 2025-ös protokollverziót használó klienseknek `Accept: application/json, text/event-stream` fejlécet kell küldeniük.', + mcpResult: 'JSON-RPC válasz', + mcpOverrideAccess: 'Hozzáférés-ellenőrzések kihagyása. Csak fejlesztéshez.', + mcpGet: 'Nem támogatott: a szerver nem nyit eseményfolyamot', + errorPluginMcpGet405: 'A metódus nem engedélyezett, használjon POST-ot', + mcpProtocolVersion: 'Az egyeztetett MCP-protokollverzió, pl. `2025-06-18`.', + mcpResult202: 'Elfogadva: a törzs csak értesítéseket vagy válaszokat tartalmazott', + errorPluginMcp404: 'Ismeretlen MCP-metódus', + errorPluginMcp406: 'Az `Accept` fejlécből hiányzik az `application/json` vagy a `text/event-stream`', + errorPluginMcp413: 'A kérés törzse túl nagy', + errorPluginMcp415: 'A `Content-Type` értéke csak `application/json` lehet', + seoTitle: 'Meta cím generálása', + seoDescription: 'Meta leírás generálása', + seoUrl: 'Előnézeti URL generálása', + seoImage: 'Meta kép generálása', + seoBody: + 'A szerkesztett dokumentum: `collectionSlug` vagy `globalSlug`, az `id` azonosítója és az aktuális `doc` adatok. A generáló függvénynek adódik át.', + seoResult: 'A generált érték. Üres karakterlánc, ha nincs generáló függvény beállítva.', + searchReindex: 'Keresési index újraépítése egyes gyűjteményekhez', + searchReindexResult: 'Az újraindexelés összegzése', + tenantOptions: 'A felhasználó által választható bérlők listája', + tenantOptionsResult: 'Bérlőopciók', + exportDownload: 'Dokumentumok exportálása fájlba', + exportDownloadResult: 'Az exportfájl', + exportPreview: 'Export előnézete', + importPreview: 'Importfájl előnézete', + previewResult: 'Előnézeti dokumentumok egy oldala', + importFileData: 'Fájltartalom, base64 kódolással.', + r2Upload: 'Fájl feltöltése az R2-be részenként', + r2UploadDesc: + 'Három lépés egy útvonalon. Kezdés: küldje el a `collection`, `fileName` és `fileType` értékeket. Minden rész: adja hozzá a `multipartId`, `multipartKey`, `multipartNumber` és `signedReceipt` értékeket, és küldje a bájtokat. Befejezés: ugyanez `multipartNumber` nélkül, a részek JSON-listájával.', + r2UploadResult: 'Feltöltés elindult, rész feltöltve vagy feltöltés kész (az objektumkulcs szövegként)', + errorStorageR2412: 'Ezzel a kulccsal már létezik fájl', localizationHeading: 'Lokalizáció', localizationNote: diff --git a/src/shared/translations/locales/hy.ts b/src/shared/translations/locales/hy.ts index 7ebd679..659fb2c 100644 --- a/src/shared/translations/locales/hy.ts +++ b/src/shared/translations/locales/hy.ts @@ -90,12 +90,27 @@ export const hy: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-ի տողի վերածված {{schema}} դաշտեր։ Օրինակ՝ `{"alt":"A caption"}`։', fileServe: 'Ֆայլը', filePartial: 'Ֆայլի մի մասը՝ `Range` հարցման համար', + paramFileVersion: 'Վերադարձնում է այս տարբերակի ID-ով պահված ֆայլը։', uploadInstructionsSummary: 'Ստանալ ֆայլի վերբեռնման հրահանգներ մինչև փաստաթղթի պահպանումը', uploadInstructionsResult: 'Որտեղ ուղարկել ֆայլի բայթերը և `file` արժեքը, որն ուղարկվում է ստեղծման կամ թարմացման հարցման հետ', uploadStagePutSummary: 'Ուղարկել ֆայլի բայթերը ժամանակավոր վերբեռնման համար', uploadStageDeleteSummary: 'Ջնջել ժամանակավոր վերբեռնումը', uploadStageResult: 'Պատրաստ է, առանց բովանդակության', + paramPreferenceKey: 'Նախընտրության բանալին, օրինակ՝ `nav`։', + preferenceGet: 'Ստանալ ընթացիկ օգտատիրոջ նախընտրությունը', + preferenceGetResult: 'Պահված նախընտրությունը կամ `value: null`, եթե այն չկա', + preferenceSet: 'Պահել ընթացիկ օգտատիրոջ նախընտրությունը', + preferenceSetBody: 'Ուղարկեք արժեքը `value`-ում։ Առանց `value`-ի մարմինը ամբողջությամբ պահվում է որպես արժեք։', + preferenceSetResult: 'Պահված նախընտրությունը', + preferenceDelete: 'Ջնջել ընթացիկ օգտատիրոջ նախընտրությունը', + preferenceDeleteResult: 'Ջնջված նախընտրությունը', + reorder: 'Տեղափոխել փաստաթղթերը դասավորվող ցանկում', + reorderBody: + 'Տեղափոխում է `docsToMove`-ը `target`-ից հետո `newKeyWillBe: "greater"`-ով կամ դրանից առաջ `"less"`-ով։ `orderableFieldName`-ը `_order` է դասավորվող կոլեկցիայի համար կամ դասավորվող join-ի կարգի դաշտը։ Առանց `target.key`-ի Payload-ը միայն կարգի բանալիներ է տալիս այն փաստաթղթերին, որոնք դրանք չունեն։', + reorderResult: + 'Կարգի նոր բանալիները կամ `message: "initial migration"`, երբ Payload-ը առաջին անգամ սահմանեց կարգի բանալիները', + errorReorder400: 'Հարցման անվավեր մարմին, պատճառը նշված է `error`-ում', error400: 'Վավերացման կամ հարցման սխալ (ValidationError, QueryError)', error401: 'Չնույնականացված (AuthenticationError)', @@ -119,6 +134,72 @@ export const hy: PluginDefaultTranslationsObject = { tagJobs: 'Առաջադրանքներ', tagUploads: 'Վերբեռնումներ', tagAccess: 'Մուտք', + tagReorder: 'Դասավորում', + tagPlugins: 'Պլագիններ', + tagPluginsDesc: 'Payload-ի պաշտոնական պլագինների ավելացրած էնդփոյնթներ։', + errorPlugin400: 'Անվավեր հարցում', + errorPlugin401: 'Նույնականացված չէ', + errorPlugin403: 'Արգելված է', + errorPlugin404: 'Չի գտնվել', + errorPlugin500: 'Սերվերի ներքին սխալ', + ecommerceAddItem: 'Ավելացնել ապրանքը զամբյուղ', + ecommerceRemoveItem: 'Հեռացնել ապրանքը զամբյուղից', + ecommerceUpdateItem: 'Փոխել զամբյուղի ապրանքի քանակը', + ecommerceClearCart: 'Հեռացնել բոլոր ապրանքները զամբյուղից', + ecommerceMergeCart: 'Միավորել հյուրի զամբյուղը այս զամբյուղի հետ', + ecommerceCartAccess: 'Թույլատրված է զամբյուղի տիրոջը կամ հյուրի զամբյուղի համար՝ հարցման մարմնում դրա `secret`-ով։', + ecommerceCartResult: 'Թարմացված զամբյուղը', + errorPluginEcommerce404: 'Զամբյուղը չի գտնվել կամ հասանելի չէ', + ecommerceQuantity: 'Նոր քանակ, կամ `{ "$inc": n }`՝ այն n-ով փոխելու համար։', + ecommerceInitiatePayment: 'Սկսել վճարում `{{method}}`-ով', + ecommerceConfirmOrder: 'Հաստատել վճարումը `{{method}}`-ով և ստեղծել պատվերը', + ecommercePaymentBody: + 'Օգտագործում է `cartID` (հյուրի զամբյուղի համար՝ `secret`-ով) կամ օգտատիրոջ զամբյուղը։ Առանց օգտատիրոջ `customerEmail`-ը պարտադիր է։ Վճարման ադապտերին կարող են պետք լինել այլ դաշտեր։', + ecommerceInitiateResult: 'Վճարումը սկսված է։ Ադապտերն ավելացնում է իր դաշտերը, օր.՝ client secret։', + ecommerceConfirmResult: 'Պատվերը ստեղծված է', + stripeWebhook: 'Ստանալ Stripe webhook իրադարձություններ', + stripeWebhookBody: + 'Stripe-ի հում իրադարձությունը՝ ստորագրված `Stripe-Signature` վերնագրում։ Կանչում է Stripe-ը, ոչ թե API հաճախորդները։', + stripeWebhookResult: 'Իրադարձությունը ստացված է', + errorPluginStripeWebhook400: 'Ստորագրության ստուգումը ձախողվեց', + stripeRest: 'Կանչել Stripe API-ի թույլատրված մեթոդ', + stripeRestResult: 'Stripe API-ի արդյունքը', + errorPluginStripeRest404: 'Stripe API-ն սխալ վերադարձրեց', + mcp: 'Ուղարկել MCP JSON-RPC հաղորդագրություն', + mcpDesc: + 'Model Context Protocol Streamable HTTP-ով՝ JSON պատասխաններով։ Անանուն հարցումներն աշխատում են. ցուցադրվող գործիքները կախված են օգտատիրոջ իրավունքներից։ 2025 թվականի արձանագրության տարբերակով հաճախորդները պետք է ուղարկեն `Accept: application/json, text/event-stream`։', + mcpResult: 'JSON-RPC պատասխան', + mcpOverrideAccess: 'Բաց թողնել մուտքի ստուգումները։ Միայն մշակման համար։', + mcpGet: 'Չի աջակցվում. սերվերը իրադարձությունների հոսք չի բացում', + errorPluginMcpGet405: 'Մեթոդը թույլատրված չէ, օգտագործեք POST', + mcpProtocolVersion: 'Համաձայնեցված MCP արձանագրության տարբերակը, օր.՝ `2025-06-18`։', + mcpResult202: 'Ընդունված է. մարմինը պարունակում էր միայն ծանուցումներ կամ պատասխաններ', + errorPluginMcp404: 'Անհայտ MCP մեթոդ', + errorPluginMcp406: '`Accept` վերնագրում բացակայում է `application/json` կամ `text/event-stream`', + errorPluginMcp413: 'Հարցման մարմինը չափազանց մեծ է', + errorPluginMcp415: '`Content-Type`-ը պետք է լինի `application/json`', + seoTitle: 'Ստեղծել մետա վերնագիրը', + seoDescription: 'Ստեղծել մետա նկարագրությունը', + seoUrl: 'Ստեղծել նախադիտման URL-ը', + seoImage: 'Ստեղծել մետա պատկերը', + seoBody: + 'Խմբագրվող փաստաթուղթը՝ `collectionSlug` կամ `globalSlug`, դրա `id`-ն և ընթացիկ `doc` տվյալները։ Փոխանցվում է ձեր ստեղծման ֆունկցիային։', + seoResult: 'Ստեղծված արժեքը։ Դատարկ տող, եթե ստեղծման ֆունկցիա սահմանված չէ։', + searchReindex: 'Վերակառուցել որոնման ինդեքսը որոշ հավաքածուների համար', + searchReindexResult: 'Վերաինդեքսավորման ամփոփում', + tenantOptions: 'Ցուցակել այն վարձակալներին, որոնց օգտատերը կարող է ընտրել', + tenantOptionsResult: 'Վարձակալների տարբերակներ', + exportDownload: 'Արտահանել փաստաթղթերը ֆայլի մեջ', + exportDownloadResult: 'Արտահանման ֆայլը', + exportPreview: 'Արտահանման նախադիտում', + importPreview: 'Ներմուծման ֆայլի նախադիտում', + previewResult: 'Նախադիտման փաստաթղթերի մեկ էջ', + importFileData: 'Ֆայլի բովանդակությունը՝ base64 կոդավորմամբ։', + r2Upload: 'Վերբեռնել ֆայլը R2 մասերով', + r2UploadDesc: + 'Երեք քայլ մեկ երթուղու վրա։ Սկիզբ՝ ուղարկեք `collection`, `fileName` և `fileType`։ Յուրաքանչյուր մաս՝ ավելացրեք `multipartId`, `multipartKey`, `multipartNumber` և `signedReceipt` և ուղարկեք բայթերը։ Ավարտ՝ նույնը առանց `multipartNumber`-ի, մասերի JSON ցուցակով։', + r2UploadResult: 'Վերբեռնումը սկսված է, մասը վերբեռնված է կամ վերբեռնումն ավարտված է (օբյեկտի բանալին՝ տեքստով)', + errorStorageR2412: 'Այս բանալիով ֆայլ արդեն կա', localizationHeading: 'Տեղայնացում', localizationNote: diff --git a/src/shared/translations/locales/id.ts b/src/shared/translations/locales/id.ts index 1db1790..d328655 100644 --- a/src/shared/translations/locales/id.ts +++ b/src/shared/translations/locales/id.ts @@ -88,12 +88,27 @@ export const id: PluginDefaultTranslationsObject = { uploadPayloadField: 'Field {{schema}} yang sudah di-JSON-stringify. Contoh: `{"alt":"A caption"}`.', fileServe: 'File', filePartial: 'Sebagian file, untuk permintaan `Range`', + paramFileVersion: 'Mengembalikan file yang disimpan dengan ID versi ini.', uploadInstructionsSummary: 'Dapatkan instruksi untuk mengunggah file sebelum menyimpan dokumen', uploadInstructionsResult: 'Ke mana byte file dikirim, dan nilai `file` yang dikirim bersama permintaan buat atau perbarui', uploadStagePutSummary: 'Kirim byte file untuk unggahan sementara', uploadStageDeleteSummary: 'Hapus unggahan sementara', uploadStageResult: 'Selesai, tanpa konten', + paramPreferenceKey: 'Kunci preferensi, mis. `nav`.', + preferenceGet: 'Ambil preferensi pengguna saat ini', + preferenceGetResult: 'Preferensi yang tersimpan, atau `value: null` jika tidak ada', + preferenceSet: 'Simpan preferensi pengguna saat ini', + preferenceSetBody: 'Kirim nilai di `value`. Body tanpa `value` disimpan utuh sebagai nilai.', + preferenceSetResult: 'Preferensi yang tersimpan', + preferenceDelete: 'Hapus preferensi pengguna saat ini', + preferenceDeleteResult: 'Preferensi yang dihapus', + reorder: 'Pindahkan dokumen dalam daftar yang dapat diurutkan', + reorderBody: + 'Memindahkan `docsToMove` ke setelah `target` dengan `newKeyWillBe: "greater"`, atau ke sebelumnya dengan `"less"`. `orderableFieldName` adalah `_order` untuk koleksi yang dapat diurutkan, atau field urutan dari join yang dapat diurutkan. Tanpa `target.key`, Payload hanya memberi kunci urutan pada dokumen yang belum memilikinya.', + reorderResult: + 'Kunci urutan baru, atau `message: "initial migration"` saat Payload pertama kali mengatur kunci urutan', + errorReorder400: 'Body permintaan tidak valid; alasannya ada di `error`', error400: 'Galat validasi atau kueri (ValidationError, QueryError)', error401: 'Tidak terautentikasi (AuthenticationError)', @@ -117,6 +132,72 @@ export const id: PluginDefaultTranslationsObject = { tagJobs: 'Job', tagUploads: 'Unggahan', tagAccess: 'Akses', + tagReorder: 'Pengurutan', + tagPlugins: 'Plugin', + tagPluginsDesc: 'Endpoint yang ditambahkan oleh plugin resmi Payload.', + errorPlugin400: 'Permintaan tidak valid', + errorPlugin401: 'Belum diautentikasi', + errorPlugin403: 'Dilarang', + errorPlugin404: 'Tidak ditemukan', + errorPlugin500: 'Kesalahan server internal', + ecommerceAddItem: 'Tambahkan item ke keranjang', + ecommerceRemoveItem: 'Hapus item dari keranjang', + ecommerceUpdateItem: 'Ubah jumlah item di keranjang', + ecommerceClearCart: 'Hapus semua item dari keranjang', + ecommerceMergeCart: 'Gabungkan keranjang tamu ke keranjang ini', + ecommerceCartAccess: 'Diizinkan untuk pemilik keranjang, atau untuk keranjang tamu dengan `secret` di body.', + ecommerceCartResult: 'Keranjang yang diperbarui', + errorPluginEcommerce404: 'Keranjang tidak ditemukan atau tidak dapat diakses', + ecommerceQuantity: 'Jumlah baru, atau `{ "$inc": n }` untuk mengubahnya sebanyak n.', + ecommerceInitiatePayment: 'Mulai pembayaran dengan `{{method}}`', + ecommerceConfirmOrder: 'Konfirmasi pembayaran dengan `{{method}}` dan buat pesanan', + ecommercePaymentBody: + 'Menggunakan `cartID` (dengan `secret` untuk keranjang tamu) atau keranjang pengguna. Tanpa pengguna, `customerEmail` wajib. Adaptor pembayaran mungkin memerlukan kolom tambahan.', + ecommerceInitiateResult: 'Pembayaran dimulai. Adaptor menambahkan kolomnya sendiri, mis. client secret.', + ecommerceConfirmResult: 'Pesanan telah dibuat', + stripeWebhook: 'Terima event webhook Stripe', + stripeWebhookBody: + 'Event Stripe mentah, ditandatangani di header `Stripe-Signature`. Dipanggil oleh Stripe, bukan oleh klien API.', + stripeWebhookResult: 'Event diterima', + errorPluginStripeWebhook400: 'Pemeriksaan tanda tangan gagal', + stripeRest: 'Panggil metode Stripe API yang diizinkan', + stripeRestResult: 'Hasil Stripe API', + errorPluginStripeRest404: 'Stripe API mengembalikan galat', + mcp: 'Kirim pesan MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol melalui Streamable HTTP, dengan respons JSON. Permintaan anonim berfungsi; alat yang ditampilkan bergantung pada akses pengguna. Klien dengan versi protokol 2025 wajib mengirim `Accept: application/json, text/event-stream`.', + mcpResult: 'Respons JSON-RPC', + mcpOverrideAccess: 'Lewati pemeriksaan akses. Hanya untuk pengembangan.', + mcpGet: 'Tidak didukung: server tidak membuka aliran event', + errorPluginMcpGet405: 'Metode tidak diizinkan, gunakan POST', + mcpProtocolVersion: 'Versi protokol MCP yang disepakati, mis. `2025-06-18`.', + mcpResult202: 'Diterima: body hanya berisi notifikasi atau respons', + errorPluginMcp404: 'Metode MCP tidak dikenal', + errorPluginMcp406: 'Header `Accept` tidak memuat `application/json` atau `text/event-stream`', + errorPluginMcp413: 'Body permintaan terlalu besar', + errorPluginMcp415: '`Content-Type` harus `application/json`', + seoTitle: 'Buat judul meta', + seoDescription: 'Buat deskripsi meta', + seoUrl: 'Buat URL pratinjau', + seoImage: 'Buat gambar meta', + seoBody: + 'Dokumen yang sedang diedit: `collectionSlug` atau `globalSlug`, `id`-nya, dan data `doc` saat ini. Diteruskan ke fungsi generate Anda.', + seoResult: 'Nilai yang dihasilkan. String kosong jika fungsi generate tidak diatur.', + searchReindex: 'Bangun ulang indeks pencarian untuk beberapa koleksi', + searchReindexResult: 'Ringkasan pengindeksan ulang', + tenantOptions: 'Daftar tenant yang dapat dipilih pengguna', + tenantOptionsResult: 'Opsi tenant', + exportDownload: 'Ekspor dokumen ke file', + exportDownloadResult: 'File ekspor', + exportPreview: 'Pratinjau ekspor', + importPreview: 'Pratinjau file impor', + previewResult: 'Satu halaman dokumen pratinjau', + importFileData: 'Isi file, dikodekan base64.', + r2Upload: 'Unggah file ke R2 secara bertahap', + r2UploadDesc: + 'Tiga langkah pada satu rute. Mulai: kirim `collection`, `fileName`, dan `fileType`. Setiap bagian: tambahkan `multipartId`, `multipartKey`, `multipartNumber`, dan `signedReceipt`, lalu kirim byte-nya. Selesai: sama tanpa `multipartNumber`, dengan daftar JSON bagian.', + r2UploadResult: 'Unggahan dimulai, bagian diunggah, atau unggahan selesai (kunci objek sebagai teks)', + errorStorageR2412: 'File dengan kunci ini sudah ada', localizationHeading: 'Localization', localizationNote: diff --git a/src/shared/translations/locales/is.ts b/src/shared/translations/locales/is.ts index 0bb4b85..a1be6aa 100644 --- a/src/shared/translations/locales/is.ts +++ b/src/shared/translations/locales/is.ts @@ -88,12 +88,27 @@ export const is: PluginDefaultTranslationsObject = { uploadPayloadField: '{{schema}}-reitir sem JSON-strengur. Dæmi: `{"alt":"A caption"}`.', fileServe: 'Skráin', filePartial: 'Hluti skrárinnar, fyrir `Range`-beiðni', + paramFileVersion: 'Skilar skránni sem var vistuð með þessu útgáfuauðkenni.', uploadInstructionsSummary: 'Sækja leiðbeiningar til að hlaða upp skrá áður en skjalið er vistað', uploadInstructionsResult: 'Hvert á að senda bæti skrárinnar og `file`-gildið sem er sent með stofnunar- eða uppfærslubeiðninni', uploadStagePutSummary: 'Senda bæti skrárinnar fyrir tímabundna upphleðslu', uploadStageDeleteSummary: 'Eyða tímabundinni upphleðslu', uploadStageResult: 'Lokið, ekkert efni', + paramPreferenceKey: 'Lykill stillingarinnar, t.d. `nav`.', + preferenceGet: 'Sækja stillingu núverandi notanda', + preferenceGetResult: 'Vistaða stillingin, eða `value: null` ef hún er ekki til', + preferenceSet: 'Vista stillingu núverandi notanda', + preferenceSetBody: 'Sendu gildið sem `value`. Meginmál án `value` er vistað í heild sem gildið.', + preferenceSetResult: 'Vistaða stillingin', + preferenceDelete: 'Eyða stillingu núverandi notanda', + preferenceDeleteResult: 'Stillingin sem var eytt', + reorder: 'Færa skjöl í raðanlegum lista', + reorderBody: + 'Færir `docsToMove` á eftir `target` með `newKeyWillBe: "greater"`, eða á undan með `"less"`. `orderableFieldName` er `_order` fyrir raðanlegt safn, eða röðunarsvið raðanlegrar tengingar. Án `target.key` gefur Payload aðeins röðunarlykla þeim skjölum sem hafa enga.', + reorderResult: + 'Nýju röðunarlyklarnir, eða `message: "initial migration"` þegar Payload setti röðunarlyklana í fyrsta sinn', + errorReorder400: 'Ógilt meginmál beiðnar; ástæðan er í `error`', error400: 'Staðfestingar- eða fyrirspurnarvilla (ValidationError, QueryError)', error401: 'Ekki auðkenndur (AuthenticationError)', @@ -117,6 +132,72 @@ export const is: PluginDefaultTranslationsObject = { tagJobs: 'Verk', tagUploads: 'Upphleðslur', tagAccess: 'Aðgangur', + tagReorder: 'Röðun', + tagPlugins: 'Viðbætur', + tagPluginsDesc: 'Endapunktar sem opinberar Payload-viðbætur bæta við.', + errorPlugin400: 'Ógild beiðni', + errorPlugin401: 'Ekki auðkenndur', + errorPlugin403: 'Bannað', + errorPlugin404: 'Fannst ekki', + errorPlugin500: 'Innri villa á þjóni', + ecommerceAddItem: 'Setja vöru í körfuna', + ecommerceRemoveItem: 'Fjarlægja vöru úr körfunni', + ecommerceUpdateItem: 'Breyta magni vöru í körfunni', + ecommerceClearCart: 'Fjarlægja allar vörur úr körfunni', + ecommerceMergeCart: 'Sameina gestakörfu við þessa körfu', + ecommerceCartAccess: 'Leyft eiganda körfunnar, eða fyrir gestakörfu með `secret` hennar í meginmáli.', + ecommerceCartResult: 'Uppfærða karfan', + errorPluginEcommerce404: 'Karfan fannst ekki eða er ekki aðgengileg', + ecommerceQuantity: 'Nýtt magn, eða `{ "$inc": n }` til að breyta því um n.', + ecommerceInitiatePayment: 'Hefja greiðslu með `{{method}}`', + ecommerceConfirmOrder: 'Staðfesta greiðsluna með `{{method}}` og stofna pöntunina', + ecommercePaymentBody: + 'Notar `cartID` (með `secret` fyrir gestakörfu) eða körfu notandans. Án notanda er `customerEmail` skylda. Greiðslumillistykkið gæti þurft fleiri reiti.', + ecommerceInitiateResult: 'Greiðsla hafin. Millistykkið bætir við eigin reitum, t.d. client secret.', + ecommerceConfirmResult: 'Pöntunin var stofnuð', + stripeWebhook: 'Taka á móti webhook-atburðum frá Stripe', + stripeWebhookBody: + 'Óunninn Stripe-atburður, undirritaður í hausnum `Stripe-Signature`. Stripe kallar á þetta, ekki API-biðlarar.', + stripeWebhookResult: 'Atburður móttekinn', + errorPluginStripeWebhook400: 'Staðfesting undirskriftar mistókst', + stripeRest: 'Kalla á leyfða aðferð í Stripe API', + stripeRestResult: 'Niðurstaða Stripe API', + errorPluginStripeRest404: 'Stripe API skilaði villu', + mcp: 'Senda MCP JSON-RPC-skilaboð', + mcpDesc: + 'Model Context Protocol yfir Streamable HTTP, með JSON-svörum. Nafnlausar beiðnir virka; verkfærin sem birtast fara eftir aðgangi notandans. Biðlarar með samskiptaregluútgáfu frá 2025 verða að senda `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC-svar', + mcpOverrideAccess: 'Sleppa aðgangsathugunum. Aðeins í þróun.', + mcpGet: 'Ekki stutt: þjónninn opnar engan atburðastraum', + errorPluginMcpGet405: 'Aðferð ekki leyfð, notaðu POST', + mcpProtocolVersion: 'Umsamin útgáfa MCP-samskiptareglunnar, t.d. `2025-06-18`.', + mcpResult202: 'Samþykkt: meginmálið innihélt aðeins tilkynningar eða svör', + errorPluginMcp404: 'Óþekkt MCP-aðferð', + errorPluginMcp406: 'Í hausinn `Accept` vantar `application/json` eða `text/event-stream`', + errorPluginMcp413: 'Meginmál beiðninnar er of stórt', + errorPluginMcp415: '`Content-Type` verður að vera `application/json`', + seoTitle: 'Búa til metatitil', + seoDescription: 'Búa til metalýsingu', + seoUrl: 'Búa til forskoðunarslóð', + seoImage: 'Búa til metamynd', + seoBody: + 'Skjalið sem verið er að breyta: `collectionSlug` eða `globalSlug`, `id` þess og núverandi `doc`-gögn. Sent áfram til generate-fallsins þíns.', + seoResult: 'Gildið sem var búið til. Tómur strengur ef ekkert generate-fall er stillt.', + searchReindex: 'Endurbyggja leitarvísinn fyrir sum söfn', + searchReindexResult: 'Samantekt endurflokkunar', + tenantOptions: 'Lista leigjendur sem notandinn getur valið', + tenantOptionsResult: 'Leigjendavalkostir', + exportDownload: 'Flytja skjöl út í skrá', + exportDownloadResult: 'Útflutningsskráin', + exportPreview: 'Forskoða útflutning', + importPreview: 'Forskoða innflutningsskrá', + previewResult: 'Síða af forskoðunarskjölum', + importFileData: 'Innihald skrár, base64-kóðað.', + r2Upload: 'Hlaða skrá upp í R2 í hlutum', + r2UploadDesc: + 'Þrjú skref á einni leið. Byrjun: sendu `collection`, `fileName` og `fileType`. Hver hluti: bættu við `multipartId`, `multipartKey`, `multipartNumber` og `signedReceipt` og sendu bætin. Lok: það sama án `multipartNumber`, með JSON-lista yfir hluta.', + r2UploadResult: 'Upphleðsla hafin, hluta hlaðið upp eða upphleðslu lokið (lykill hlutarins sem texti)', + errorStorageR2412: 'Skrá er þegar til með þessum lykli', localizationHeading: 'Staðfærsla', localizationNote: diff --git a/src/shared/translations/locales/it.ts b/src/shared/translations/locales/it.ts index 97421fa..9185cd5 100644 --- a/src/shared/translations/locales/it.ts +++ b/src/shared/translations/locales/it.ts @@ -89,12 +89,27 @@ export const it: PluginDefaultTranslationsObject = { uploadPayloadField: 'Campi {{schema}} in formato JSON. Esempio: `{"alt":"A caption"}`.', fileServe: 'Il file', filePartial: 'Una parte del file, per una richiesta `Range`', + paramFileVersion: 'Restituisce il file salvato con questo ID di versione.', uploadInstructionsSummary: 'Ottiene le istruzioni per caricare un file prima di salvare il documento', uploadInstructionsResult: 'Dove inviare i byte del file e il valore `file` da inviare con la richiesta di creazione o aggiornamento', uploadStagePutSummary: 'Invia i byte del file per un caricamento temporaneo', uploadStageDeleteSummary: 'Elimina un caricamento temporaneo', uploadStageResult: 'Fatto, nessun contenuto', + paramPreferenceKey: 'La chiave della preferenza, ad es. `nav`.', + preferenceGet: "Ottiene una preferenza dell'utente corrente", + preferenceGetResult: 'La preferenza salvata, oppure `value: null` se non esiste', + preferenceSet: "Salva una preferenza dell'utente corrente", + preferenceSetBody: "Inviare il valore in `value`. Un corpo senza `value` viene salvato così com'è come valore.", + preferenceSetResult: 'La preferenza salvata', + preferenceDelete: "Elimina una preferenza dell'utente corrente", + preferenceDeleteResult: 'La preferenza eliminata', + reorder: 'Sposta documenti in un elenco ordinabile', + reorderBody: + 'Sposta `docsToMove` dopo `target` con `newKeyWillBe: "greater"`, oppure prima con `"less"`. `orderableFieldName` è `_order` per una collezione ordinabile, oppure il campo di ordinamento di un join ordinabile. Senza `target.key`, Payload assegna le chiavi di ordinamento solo ai documenti che non ne hanno.', + reorderResult: + 'Le nuove chiavi di ordinamento, oppure `message: "initial migration"` quando Payload ha impostato le chiavi di ordinamento per la prima volta', + errorReorder400: 'Corpo della richiesta non valido; il motivo è in `error`', error400: 'Errore di validazione o di query (ValidationError, QueryError)', error401: 'Non autenticato (AuthenticationError)', @@ -118,6 +133,73 @@ export const it: PluginDefaultTranslationsObject = { tagJobs: 'Job', tagUploads: 'Caricamenti', tagAccess: 'Accesso', + tagReorder: 'Riordinamento', + tagPlugins: 'Plugin', + tagPluginsDesc: 'Endpoint aggiunti dai plugin ufficiali di Payload.', + errorPlugin400: 'Richiesta non valida', + errorPlugin401: 'Non autenticato', + errorPlugin403: 'Accesso negato', + errorPlugin404: 'Non trovato', + errorPlugin500: 'Errore interno del server', + ecommerceAddItem: 'Aggiungi un articolo al carrello', + ecommerceRemoveItem: 'Rimuovi un articolo dal carrello', + ecommerceUpdateItem: 'Modifica la quantità di un articolo del carrello', + ecommerceClearCart: 'Rimuovi tutti gli articoli dal carrello', + ecommerceMergeCart: 'Unisci un carrello ospite a questo carrello', + ecommerceCartAccess: + 'Consentito al proprietario del carrello, o per un carrello ospite con il suo `secret` nel corpo.', + ecommerceCartResult: 'Il carrello aggiornato', + errorPluginEcommerce404: 'Carrello non trovato o non accessibile', + ecommerceQuantity: 'Nuova quantità, oppure `{ "$inc": n }` per modificarla di n.', + ecommerceInitiatePayment: 'Avvia un pagamento con `{{method}}`', + ecommerceConfirmOrder: 'Conferma il pagamento con `{{method}}` e crea l’ordine', + ecommercePaymentBody: + 'Usa `cartID` (con `secret` per un carrello ospite) o il carrello dell’utente. Senza utente, `customerEmail` è obbligatorio. L’adattatore di pagamento può richiedere altri campi.', + ecommerceInitiateResult: 'Pagamento avviato. L’adattatore aggiunge i propri campi, ad es. un client secret.', + ecommerceConfirmResult: 'L’ordine è stato creato', + stripeWebhook: 'Ricevi gli eventi webhook di Stripe', + stripeWebhookBody: + 'L’evento Stripe grezzo, firmato nell’header `Stripe-Signature`. Chiamato da Stripe, non dai client dell’API.', + stripeWebhookResult: 'Evento ricevuto', + errorPluginStripeWebhook400: 'Verifica della firma non riuscita', + stripeRest: 'Chiama un metodo consentito dell’API Stripe', + stripeRestResult: 'Il risultato dell’API Stripe', + errorPluginStripeRest404: 'L’API Stripe ha restituito un errore', + mcp: 'Invia un messaggio MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol su Streamable HTTP, con risposte JSON. Le richieste anonime funzionano; gli strumenti elencati dipendono dai permessi dell’utente. I client con una versione del protocollo del 2025 devono inviare `Accept: application/json, text/event-stream`.', + mcpResult: 'Risposta JSON-RPC', + mcpOverrideAccess: 'Salta i controlli di accesso. Solo per lo sviluppo.', + mcpGet: 'Non supportato: il server non apre alcun flusso di eventi', + errorPluginMcpGet405: 'Metodo non consentito, usa POST', + mcpProtocolVersion: 'Versione negoziata del protocollo MCP, ad es. `2025-06-18`.', + mcpResult202: 'Accettato: il corpo conteneva solo notifiche o risposte', + errorPluginMcp404: 'Metodo MCP sconosciuto', + errorPluginMcp406: 'Nell’header `Accept` manca `application/json` o `text/event-stream`', + errorPluginMcp413: 'Il corpo della richiesta è troppo grande', + errorPluginMcp415: '`Content-Type` deve essere `application/json`', + seoTitle: 'Genera il meta titolo', + seoDescription: 'Genera la meta descrizione', + seoUrl: 'Genera l’URL di anteprima', + seoImage: 'Genera la meta immagine', + seoBody: + 'Il documento in modifica: `collectionSlug` o `globalSlug`, il suo `id` e i dati `doc` correnti. Passato alla tua funzione di generazione.', + seoResult: 'Il valore generato. Una stringa vuota se non è impostata alcuna funzione di generazione.', + searchReindex: 'Ricostruisci l’indice di ricerca per alcune collezioni', + searchReindexResult: 'Riepilogo della reindicizzazione', + tenantOptions: 'Elenca i tenant che l’utente può scegliere', + tenantOptionsResult: 'Opzioni tenant', + exportDownload: 'Esporta documenti in un file', + exportDownloadResult: 'Il file esportato', + exportPreview: 'Anteprima di un’esportazione', + importPreview: 'Anteprima di un file di importazione', + previewResult: 'Una pagina di documenti di anteprima', + importFileData: 'Contenuto del file, codificato in base64.', + r2Upload: 'Carica un file su R2 in parti', + r2UploadDesc: + 'Tre passaggi su una sola route. Avvio: invia `collection`, `fileName` e `fileType`. Ogni parte: aggiungi `multipartId`, `multipartKey`, `multipartNumber` e `signedReceipt`, e invia i byte. Completamento: lo stesso senza `multipartNumber`, con l’elenco JSON delle parti.', + r2UploadResult: 'Caricamento avviato, parte caricata o caricamento completato (la chiave dell’oggetto come testo)', + errorStorageR2412: 'Esiste già un file con questa chiave', localizationHeading: 'Localizzazione', localizationNote: diff --git a/src/shared/translations/locales/ja.ts b/src/shared/translations/locales/ja.ts index fbdd097..1ac85a4 100644 --- a/src/shared/translations/locales/ja.ts +++ b/src/shared/translations/locales/ja.ts @@ -89,11 +89,25 @@ export const ja: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON 文字列化した {{schema}} フィールド。例: `{"alt":"A caption"}`。', fileServe: 'ファイル', filePartial: '`Range` リクエストに対するファイルの一部', + paramFileVersion: 'このバージョン ID で保存されたファイルを返します。', uploadInstructionsSummary: 'ドキュメントを保存する前にファイルをアップロードする手順を取得します', uploadInstructionsResult: 'ファイルのバイトの送信先と、作成または更新リクエストで送信する `file` の値', uploadStagePutSummary: '一時アップロードにファイルのバイトを送信します', uploadStageDeleteSummary: '一時アップロードを削除します', uploadStageResult: '完了 (コンテンツなし)', + paramPreferenceKey: '設定のキー (例: `nav`)。', + preferenceGet: '現在のユーザーの設定を取得', + preferenceGetResult: '保存された設定。存在しない場合は `value: null`', + preferenceSet: '現在のユーザーの設定を保存', + preferenceSetBody: '値は `value` で送信します。`value` のない本文は、本文全体が値として保存されます。', + preferenceSetResult: '保存された設定', + preferenceDelete: '現在のユーザーの設定を削除', + preferenceDeleteResult: '削除された設定', + reorder: '並べ替え可能なリスト内でドキュメントを移動', + reorderBody: + '`newKeyWillBe: "greater"` の場合は `docsToMove` を `target` の後ろへ、`"less"` の場合は前へ移動します。`orderableFieldName` は、並べ替え可能なコレクションでは `_order`、並べ替え可能な join ではその並び順フィールドです。`target.key` がない場合、Payload は並び順キーを持たないドキュメントにキーを割り当てるだけです。', + reorderResult: '新しい並び順キー。Payload が初めて並び順キーを設定した場合は `message: "initial migration"`', + errorReorder400: 'リクエスト本文が無効です。理由は `error` にあります', error400: '検証エラーまたはクエリエラー (ValidationError, QueryError)', error401: '認証されていません (AuthenticationError)', @@ -117,6 +131,73 @@ export const ja: PluginDefaultTranslationsObject = { tagJobs: 'ジョブ', tagUploads: 'アップロード', tagAccess: 'アクセス', + tagReorder: '並べ替え', + tagPlugins: 'プラグイン', + tagPluginsDesc: 'Payload 公式プラグインが追加するエンドポイント。', + errorPlugin400: '不正なリクエスト', + errorPlugin401: '未認証', + errorPlugin403: 'アクセス禁止', + errorPlugin404: '見つかりません', + errorPlugin500: 'サーバー内部エラー', + ecommerceAddItem: 'カートに商品を追加', + ecommerceRemoveItem: 'カートから商品を削除', + ecommerceUpdateItem: 'カート内の商品の数量を変更', + ecommerceClearCart: 'カートの商品をすべて削除', + ecommerceMergeCart: 'ゲストカートをこのカートに統合', + ecommerceCartAccess: 'カートの所有者、またはリクエスト本文に `secret` を含むゲストカートに許可されます。', + ecommerceCartResult: '更新されたカート', + errorPluginEcommerce404: 'カートが見つからないか、アクセスできません', + ecommerceQuantity: '新しい数量、または n だけ増減する `{ "$inc": n }`。', + ecommerceInitiatePayment: '`{{method}}` で決済を開始', + ecommerceConfirmOrder: '`{{method}}` で決済を確定して注文を作成', + ecommercePaymentBody: + '`cartID`(ゲストカートの場合は `secret` も)またはユーザーのカートを使います。ユーザーがいない場合は `customerEmail` が必須です。決済アダプターによっては追加のフィールドが必要です。', + ecommerceInitiateResult: '決済を開始しました。アダプターは client secret などの独自フィールドを追加します。', + ecommerceConfirmResult: '注文が作成されました', + stripeWebhook: 'Stripe の Webhook イベントを受信', + stripeWebhookBody: + '`Stripe-Signature` ヘッダーで署名された Stripe の生イベント。API クライアントではなく Stripe が呼び出します。', + stripeWebhookResult: 'イベントを受信しました', + errorPluginStripeWebhook400: '署名の検証に失敗しました', + stripeRest: '許可された Stripe API メソッドを呼び出す', + stripeRestResult: 'Stripe API の結果', + errorPluginStripeRest404: 'Stripe API がエラーを返しました', + mcp: 'MCP JSON-RPC メッセージを送信', + mcpDesc: + 'Streamable HTTP 上の Model Context Protocol で、JSON で応答します。匿名リクエストも使えます。表示されるツールはユーザーの権限によって変わります。2025 年のプロトコルバージョンを使うクライアントは `Accept: application/json, text/event-stream` を送信する必要があります。', + mcpResult: 'JSON-RPC レスポンス', + mcpOverrideAccess: 'アクセスチェックをスキップします。開発専用です。', + mcpGet: '未対応: サーバーはイベントストリームを開きません', + errorPluginMcpGet405: '許可されていないメソッドです。POST を使ってください', + mcpProtocolVersion: 'ネゴシエートされた MCP プロトコルバージョン。例: `2025-06-18`。', + mcpResult202: '受理: 本文には通知またはレスポンスのみが含まれていました', + errorPluginMcp404: '不明な MCP メソッド', + errorPluginMcp406: '`Accept` ヘッダーに `application/json` または `text/event-stream` がありません', + errorPluginMcp413: 'リクエスト本文が大きすぎます', + errorPluginMcp415: '`Content-Type` は `application/json` である必要があります', + seoTitle: 'メタタイトルを生成', + seoDescription: 'メタディスクリプションを生成', + seoUrl: 'プレビュー URL を生成', + seoImage: 'メタ画像を生成', + seoBody: + '編集中のドキュメント: `collectionSlug` または `globalSlug`、その `id`、現在の `doc` データ。生成関数に渡されます。', + seoResult: '生成された値。生成関数が設定されていない場合は空文字列です。', + searchReindex: '一部のコレクションの検索インデックスを再構築', + searchReindexResult: '再インデックスの概要', + tenantOptions: 'ユーザーが選択できるテナントの一覧', + tenantOptionsResult: 'テナントの選択肢', + exportDownload: 'ドキュメントをファイルにエクスポート', + exportDownloadResult: 'エクスポートファイル', + exportPreview: 'エクスポートのプレビュー', + importPreview: 'インポートファイルのプレビュー', + previewResult: 'プレビュードキュメントの 1 ページ', + importFileData: 'base64 でエンコードしたファイル内容。', + r2Upload: 'R2 にファイルを分割アップロード', + r2UploadDesc: + '1 つのルートで 3 ステップ。開始: `collection`、`fileName`、`fileType` を送信。各パート: `multipartId`、`multipartKey`、`multipartNumber`、`signedReceipt` を追加してバイト列を送信。完了: `multipartNumber` を除いた同じパラメーターで、パートの JSON リストを送信。', + r2UploadResult: + 'アップロード開始、パートのアップロード、またはアップロード完了(オブジェクトキーをテキストで返す)', + errorStorageR2412: 'このキーのファイルはすでに存在します', localizationHeading: 'ローカライズ', localizationNote: diff --git a/src/shared/translations/locales/ko.ts b/src/shared/translations/locales/ko.ts index b10d0f3..86dc6bf 100644 --- a/src/shared/translations/locales/ko.ts +++ b/src/shared/translations/locales/ko.ts @@ -87,11 +87,25 @@ export const ko: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON 문자열로 변환된 {{schema}} 필드입니다. 예: `{"alt":"A caption"}`.', fileServe: '파일', filePartial: '`Range` 요청에 대한 파일의 일부', + paramFileVersion: '이 버전 ID로 저장된 파일을 반환합니다.', uploadInstructionsSummary: '문서를 저장하기 전에 파일 업로드 안내를 받습니다', uploadInstructionsResult: '파일 바이트를 보낼 위치와 생성 또는 수정 요청에 함께 보낼 `file` 값', uploadStagePutSummary: '임시 업로드에 파일 바이트를 보냅니다', uploadStageDeleteSummary: '임시 업로드를 삭제합니다', uploadStageResult: '완료, 콘텐츠 없음', + paramPreferenceKey: '환경설정 키입니다. 예: `nav`.', + preferenceGet: '현재 사용자의 환경설정을 가져옵니다', + preferenceGetResult: '저장된 환경설정 또는 없으면 `value: null`', + preferenceSet: '현재 사용자의 환경설정을 저장합니다', + preferenceSetBody: '값은 `value`로 보냅니다. `value`가 없는 본문은 본문 전체가 값으로 저장됩니다.', + preferenceSetResult: '저장된 환경설정', + preferenceDelete: '현재 사용자의 환경설정을 삭제합니다', + preferenceDeleteResult: '삭제된 환경설정', + reorder: '정렬 가능한 목록에서 문서를 이동합니다', + reorderBody: + '`newKeyWillBe: "greater"`이면 `docsToMove`를 `target` 뒤로, `"less"`이면 앞으로 이동합니다. `orderableFieldName`은 정렬 가능한 컬렉션에서는 `_order`, 정렬 가능한 join에서는 해당 정렬 필드입니다. `target.key`가 없으면 Payload는 정렬 키가 없는 문서에 정렬 키만 부여합니다.', + reorderResult: '새 정렬 키, 또는 Payload가 처음으로 정렬 키를 설정한 경우 `message: "initial migration"`', + errorReorder400: '요청 본문이 잘못되었습니다. 이유는 `error`에 있습니다', error400: '유효성 검사 또는 쿼리 오류 (ValidationError, QueryError)', error401: '인증되지 않음 (AuthenticationError)', @@ -115,6 +129,72 @@ export const ko: PluginDefaultTranslationsObject = { tagJobs: '작업', tagUploads: '업로드', tagAccess: '접근', + tagReorder: '정렬', + tagPlugins: '플러그인', + tagPluginsDesc: 'Payload 공식 플러그인이 추가하는 엔드포인트입니다.', + errorPlugin400: '잘못된 요청', + errorPlugin401: '인증되지 않음', + errorPlugin403: '금지됨', + errorPlugin404: '찾을 수 없음', + errorPlugin500: '내부 서버 오류', + ecommerceAddItem: '장바구니에 상품 추가', + ecommerceRemoveItem: '장바구니에서 상품 제거', + ecommerceUpdateItem: '장바구니 상품의 수량 변경', + ecommerceClearCart: '장바구니의 모든 상품 제거', + ecommerceMergeCart: '게스트 장바구니를 이 장바구니에 병합', + ecommerceCartAccess: '장바구니 소유자 또는 요청 본문에 `secret`이 있는 게스트 장바구니에 허용됩니다.', + ecommerceCartResult: '업데이트된 장바구니', + errorPluginEcommerce404: '장바구니를 찾을 수 없거나 접근할 수 없음', + ecommerceQuantity: '새 수량 또는 n만큼 변경하려면 `{ "$inc": n }`.', + ecommerceInitiatePayment: '`{{method}}`(으)로 결제 시작', + ecommerceConfirmOrder: '`{{method}}`(으)로 결제를 확인하고 주문 생성', + ecommercePaymentBody: + '`cartID`(게스트 장바구니는 `secret` 포함) 또는 사용자의 장바구니를 사용합니다. 사용자가 없으면 `customerEmail`이 필수입니다. 결제 어댑터에 따라 추가 필드가 필요할 수 있습니다.', + ecommerceInitiateResult: '결제가 시작되었습니다. 어댑터가 client secret 같은 자체 필드를 추가합니다.', + ecommerceConfirmResult: '주문이 생성되었습니다', + stripeWebhook: 'Stripe 웹훅 이벤트 수신', + stripeWebhookBody: + '`Stripe-Signature` 헤더로 서명된 Stripe 원본 이벤트입니다. API 클라이언트가 아니라 Stripe가 호출합니다.', + stripeWebhookResult: '이벤트 수신됨', + errorPluginStripeWebhook400: '서명 확인에 실패했습니다', + stripeRest: '허용된 Stripe API 메서드 호출', + stripeRestResult: 'Stripe API 결과', + errorPluginStripeRest404: 'Stripe API가 오류를 반환했습니다', + mcp: 'MCP JSON-RPC 메시지 전송', + mcpDesc: + 'Streamable HTTP 기반 Model Context Protocol이며 JSON으로 응답합니다. 익명 요청도 동작하며, 표시되는 도구는 사용자 권한에 따라 다릅니다. 2025년 프로토콜 버전을 사용하는 클라이언트는 `Accept: application/json, text/event-stream`을 보내야 합니다.', + mcpResult: 'JSON-RPC 응답', + mcpOverrideAccess: '접근 검사를 건너뜁니다. 개발 전용입니다.', + mcpGet: '지원되지 않음: 서버는 이벤트 스트림을 열지 않습니다', + errorPluginMcpGet405: '허용되지 않은 메서드입니다. POST를 사용하세요', + mcpProtocolVersion: '협상된 MCP 프로토콜 버전입니다. 예: `2025-06-18`.', + mcpResult202: '수락됨: 본문에 알림 또는 응답만 있었습니다', + errorPluginMcp404: '알 수 없는 MCP 메서드', + errorPluginMcp406: '`Accept` 헤더에 `application/json` 또는 `text/event-stream`이 없습니다', + errorPluginMcp413: '요청 본문이 너무 큽니다', + errorPluginMcp415: '`Content-Type`은 `application/json`이어야 합니다', + seoTitle: '메타 제목 생성', + seoDescription: '메타 설명 생성', + seoUrl: '미리보기 URL 생성', + seoImage: '메타 이미지 생성', + seoBody: + '편집 중인 문서: `collectionSlug` 또는 `globalSlug`, 해당 `id`, 현재 `doc` 데이터. 생성 함수에 전달됩니다.', + seoResult: '생성된 값입니다. 생성 함수가 설정되지 않으면 빈 문자열입니다.', + searchReindex: '일부 컬렉션의 검색 인덱스 재구성', + searchReindexResult: '재색인 요약', + tenantOptions: '사용자가 선택할 수 있는 테넌트 목록', + tenantOptionsResult: '테넌트 옵션', + exportDownload: '문서를 파일로 내보내기', + exportDownloadResult: '내보낸 파일', + exportPreview: '내보내기 미리보기', + importPreview: '가져오기 파일 미리보기', + previewResult: '미리보기 문서 한 페이지', + importFileData: 'base64로 인코딩된 파일 내용입니다.', + r2Upload: 'R2에 파일을 여러 부분으로 업로드', + r2UploadDesc: + '하나의 경로에서 세 단계로 진행합니다. 시작: `collection`, `fileName`, `fileType`을 보냅니다. 각 부분: `multipartId`, `multipartKey`, `multipartNumber`, `signedReceipt`를 추가하고 바이트를 보냅니다. 완료: `multipartNumber` 없이 같은 값과 부분 목록 JSON을 보냅니다.', + r2UploadResult: '업로드 시작, 부분 업로드 또는 업로드 완료(객체 키를 텍스트로 반환)', + errorStorageR2412: '이 키에 이미 파일이 있습니다', localizationHeading: '현지화', localizationNote: diff --git a/src/shared/translations/locales/lt.ts b/src/shared/translations/locales/lt.ts index 2c3219d..b300a45 100644 --- a/src/shared/translations/locales/lt.ts +++ b/src/shared/translations/locales/lt.ts @@ -87,12 +87,27 @@ export const lt: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON eilute pavaizduoti {{schema}} laukai. Pavyzdys: `{"alt":"A caption"}`.', fileServe: 'Failas', filePartial: 'Failo dalis `Range` užklausai', + paramFileVersion: 'Grąžina failą, išsaugotą su šiuo versijos ID.', uploadInstructionsSummary: 'Gauti failo įkėlimo instrukcijas prieš išsaugant dokumentą', uploadInstructionsResult: 'Kur siųsti failo baitus ir kokią `file` reikšmę siųsti su kūrimo arba atnaujinimo užklausa', uploadStagePutSummary: 'Siųsti failo baitus laikinam įkėlimui', uploadStageDeleteSummary: 'Ištrinti laikiną įkėlimą', uploadStageResult: 'Atlikta, be turinio', + paramPreferenceKey: 'Nuostatos raktas, pvz., `nav`.', + preferenceGet: 'Gauti dabartinio naudotojo nuostatą', + preferenceGetResult: 'Išsaugota nuostata arba `value: null`, jei jos nėra', + preferenceSet: 'Išsaugoti dabartinio naudotojo nuostatą', + preferenceSetBody: 'Siųskite reikšmę lauke `value`. Turinys be `value` išsaugomas visas kaip reikšmė.', + preferenceSetResult: 'Išsaugota nuostata', + preferenceDelete: 'Ištrinti dabartinio naudotojo nuostatą', + preferenceDeleteResult: 'Ištrinta nuostata', + reorder: 'Perkelti dokumentus rikiuojamame sąraše', + reorderBody: + 'Perkelia `docsToMove` po `target`, kai `newKeyWillBe: "greater"`, arba prieš jį, kai `"less"`. `orderableFieldName` yra `_order` rikiuojamai kolekcijai arba rikiuojamo join tvarkos laukas. Be `target.key` Payload tik priskiria tvarkos raktus dokumentams, kurie jų neturi.', + reorderResult: + 'Nauji tvarkos raktai arba `message: "initial migration"`, kai Payload pirmą kartą nustatė tvarkos raktus', + errorReorder400: 'Netinkamas užklausos turinys; priežastis nurodyta `error`', error400: 'Patvirtinimo arba užklausos klaida (ValidationError, QueryError)', error401: 'Neautentifikuota (AuthenticationError)', @@ -116,6 +131,72 @@ export const lt: PluginDefaultTranslationsObject = { tagJobs: 'Užduotys', tagUploads: 'Įkėlimai', tagAccess: 'Prieiga', + tagReorder: 'Tvarka', + tagPlugins: 'Papildiniai', + tagPluginsDesc: 'Oficialių Payload papildinių pridėti galiniai taškai.', + errorPlugin400: 'Netinkama užklausa', + errorPlugin401: 'Neautentifikuota', + errorPlugin403: 'Draudžiama', + errorPlugin404: 'Nerasta', + errorPlugin500: 'Vidinė serverio klaida', + ecommerceAddItem: 'Įdėti prekę į krepšelį', + ecommerceRemoveItem: 'Pašalinti prekę iš krepšelio', + ecommerceUpdateItem: 'Pakeisti krepšelio prekės kiekį', + ecommerceClearCart: 'Pašalinti visas prekes iš krepšelio', + ecommerceMergeCart: 'Sujungti svečio krepšelį su šiuo krepšeliu', + ecommerceCartAccess: 'Leidžiama krepšelio savininkui arba svečio krepšeliui su jo `secret` užklausos turinyje.', + ecommerceCartResult: 'Atnaujintas krepšelis', + errorPluginEcommerce404: 'Krepšelis nerastas arba nepasiekiamas', + ecommerceQuantity: 'Naujas kiekis arba `{ "$inc": n }`, kad jį pakeistumėte n.', + ecommerceInitiatePayment: 'Pradėti mokėjimą per `{{method}}`', + ecommerceConfirmOrder: 'Patvirtinti mokėjimą per `{{method}}` ir sukurti užsakymą', + ecommercePaymentBody: + 'Naudoja `cartID` (su `secret` svečio krepšeliui) arba naudotojo krepšelį. Be naudotojo `customerEmail` yra privalomas. Mokėjimo adapteriui gali reikėti daugiau laukų.', + ecommerceInitiateResult: 'Mokėjimas pradėtas. Adapteris prideda savo laukus, pvz., client secret.', + ecommerceConfirmResult: 'Užsakymas sukurtas', + stripeWebhook: 'Gauti Stripe webhook įvykius', + stripeWebhookBody: + 'Neapdorotas Stripe įvykis, pasirašytas antraštėje `Stripe-Signature`. Jį kviečia Stripe, ne API klientai.', + stripeWebhookResult: 'Įvykis gautas', + errorPluginStripeWebhook400: 'Parašo patikra nepavyko', + stripeRest: 'Iškviesti leidžiamą Stripe API metodą', + stripeRestResult: 'Stripe API rezultatas', + errorPluginStripeRest404: 'Stripe API grąžino klaidą', + mcp: 'Siųsti MCP JSON-RPC pranešimą', + mcpDesc: + 'Model Context Protocol per Streamable HTTP su JSON atsakymais. Anoniminės užklausos veikia; išvardyti įrankiai priklauso nuo naudotojo teisių. Klientai, naudojantys 2025 m. protokolo versiją, turi siųsti `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC atsakymas', + mcpOverrideAccess: 'Praleisti prieigos patikras. Tik kūrimui.', + mcpGet: 'Nepalaikoma: serveris neatidaro įvykių srauto', + errorPluginMcpGet405: 'Metodas neleidžiamas, naudokite POST', + mcpProtocolVersion: 'Suderinta MCP protokolo versija, pvz., `2025-06-18`.', + mcpResult202: 'Priimta: turinyje buvo tik pranešimai arba atsakymai', + errorPluginMcp404: 'Nežinomas MCP metodas', + errorPluginMcp406: 'Antraštėje `Accept` nėra `application/json` arba `text/event-stream`', + errorPluginMcp413: 'Užklausos turinys per didelis', + errorPluginMcp415: '`Content-Type` turi būti `application/json`', + seoTitle: 'Generuoti meta pavadinimą', + seoDescription: 'Generuoti meta aprašą', + seoUrl: 'Generuoti peržiūros URL', + seoImage: 'Generuoti meta paveikslėlį', + seoBody: + 'Redaguojamas dokumentas: `collectionSlug` arba `globalSlug`, jo `id` ir dabartiniai `doc` duomenys. Perduodamas jūsų generavimo funkcijai.', + seoResult: 'Sugeneruota reikšmė. Tuščia eilutė, jei generavimo funkcija nenustatyta.', + searchReindex: 'Iš naujo sukurti paieškos indeksą kai kurioms kolekcijoms', + searchReindexResult: 'Perindeksavimo santrauka', + tenantOptions: 'Išvardyti nuomininkus, kuriuos naudotojas gali pasirinkti', + tenantOptionsResult: 'Nuomininkų parinktys', + exportDownload: 'Eksportuoti dokumentus į failą', + exportDownloadResult: 'Eksporto failas', + exportPreview: 'Eksporto peržiūra', + importPreview: 'Importo failo peržiūra', + previewResult: 'Peržiūros dokumentų puslapis', + importFileData: 'Failo turinys, užkoduotas base64.', + r2Upload: 'Įkelti failą į R2 dalimis', + r2UploadDesc: + 'Trys žingsniai viename maršrute. Pradžia: siųskite `collection`, `fileName` ir `fileType`. Kiekviena dalis: pridėkite `multipartId`, `multipartKey`, `multipartNumber` ir `signedReceipt` ir siųskite baitus. Pabaiga: tas pats be `multipartNumber`, su dalių JSON sąrašu.', + r2UploadResult: 'Įkėlimas pradėtas, dalis įkelta arba įkėlimas baigtas (objekto raktas tekstu)', + errorStorageR2412: 'Failas su šiuo raktu jau egzistuoja', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/shared/translations/locales/lv.ts b/src/shared/translations/locales/lv.ts index 7b4034f..9c25b1e 100644 --- a/src/shared/translations/locales/lv.ts +++ b/src/shared/translations/locales/lv.ts @@ -88,12 +88,27 @@ export const lv: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON virknē pārveidotie {{schema}} lauki. Piemērs: `{"alt":"A caption"}`.', fileServe: 'Fails', filePartial: 'Faila daļa `Range` pieprasījumam', + paramFileVersion: 'Atgriež failu, kas saglabāts ar šo versijas ID.', uploadInstructionsSummary: 'Saņemt norādījumus faila augšupielādei pirms dokumenta saglabāšanas', uploadInstructionsResult: 'Kur sūtīt faila baitus un kāda `file` vērtība jāsūta kopā ar izveides vai atjaunināšanas pieprasījumu', uploadStagePutSummary: 'Sūtīt faila baitus pagaidu augšupielādei', uploadStageDeleteSummary: 'Dzēst pagaidu augšupielādi', uploadStageResult: 'Gatavs, bez satura', + paramPreferenceKey: 'Iestatījuma atslēga, piem., `nav`.', + preferenceGet: 'Iegūt pašreizējā lietotāja iestatījumu', + preferenceGetResult: 'Saglabātais iestatījums vai `value: null`, ja tāda nav', + preferenceSet: 'Saglabāt pašreizējā lietotāja iestatījumu', + preferenceSetBody: 'Sūtiet vērtību laukā `value`. Pamatteksts bez `value` tiek saglabāts kā vērtība pilnībā.', + preferenceSetResult: 'Saglabātais iestatījums', + preferenceDelete: 'Dzēst pašreizējā lietotāja iestatījumu', + preferenceDeleteResult: 'Dzēstais iestatījums', + reorder: 'Pārvietot dokumentus kārtojamā sarakstā', + reorderBody: + 'Pārvieto `docsToMove` aiz `target` ar `newKeyWillBe: "greater"` vai pirms tā ar `"less"`. `orderableFieldName` ir `_order` kārtojamai kolekcijai vai kārtojama join secības lauks. Bez `target.key` Payload tikai piešķir secības atslēgas dokumentiem, kuriem to nav.', + reorderResult: + 'Jaunās secības atslēgas vai `message: "initial migration"`, kad Payload pirmo reizi iestatīja secības atslēgas', + errorReorder400: 'Nederīgs pieprasījuma pamatteksts; iemesls ir `error`', error400: 'Validācijas vai vaicājuma kļūda (ValidationError, QueryError)', error401: 'Nav autentificēts (AuthenticationError)', @@ -117,6 +132,72 @@ export const lv: PluginDefaultTranslationsObject = { tagJobs: 'Darbi', tagUploads: 'Augšupielādes', tagAccess: 'Piekļuve', + tagReorder: 'Secība', + tagPlugins: 'Spraudņi', + tagPluginsDesc: 'Galapunkti, ko pievieno oficiālie Payload spraudņi.', + errorPlugin400: 'Nederīgs pieprasījums', + errorPlugin401: 'Nav autentificēts', + errorPlugin403: 'Aizliegts', + errorPlugin404: 'Nav atrasts', + errorPlugin500: 'Iekšēja servera kļūda', + ecommerceAddItem: 'Pievienot preci grozam', + ecommerceRemoveItem: 'Izņemt preci no groza', + ecommerceUpdateItem: 'Mainīt preces daudzumu grozā', + ecommerceClearCart: 'Izņemt visas preces no groza', + ecommerceMergeCart: 'Apvienot viesa grozu ar šo grozu', + ecommerceCartAccess: 'Atļauts groza īpašniekam vai viesa grozam ar tā `secret` pieprasījuma ķermenī.', + ecommerceCartResult: 'Atjauninātais grozs', + errorPluginEcommerce404: 'Grozs nav atrasts vai nav pieejams', + ecommerceQuantity: 'Jaunais daudzums vai `{ "$inc": n }`, lai to mainītu par n.', + ecommerceInitiatePayment: 'Sākt maksājumu ar `{{method}}`', + ecommerceConfirmOrder: 'Apstiprināt maksājumu ar `{{method}}` un izveidot pasūtījumu', + ecommercePaymentBody: + 'Izmanto `cartID` (ar `secret` viesa grozam) vai lietotāja grozu. Bez lietotāja `customerEmail` ir obligāts. Maksājumu adapterim var būt vajadzīgi vēl lauki.', + ecommerceInitiateResult: 'Maksājums sākts. Adapteris pievieno savus laukus, piem., client secret.', + ecommerceConfirmResult: 'Pasūtījums izveidots', + stripeWebhook: 'Saņemt Stripe webhook notikumus', + stripeWebhookBody: + 'Neapstrādāts Stripe notikums, parakstīts galvenē `Stripe-Signature`. To izsauc Stripe, nevis API klienti.', + stripeWebhookResult: 'Notikums saņemts', + errorPluginStripeWebhook400: 'Paraksta pārbaude neizdevās', + stripeRest: 'Izsaukt atļautu Stripe API metodi', + stripeRestResult: 'Stripe API rezultāts', + errorPluginStripeRest404: 'Stripe API atgrieza kļūdu', + mcp: 'Nosūtīt MCP JSON-RPC ziņojumu', + mcpDesc: + 'Model Context Protocol caur Streamable HTTP ar JSON atbildēm. Anonīmi pieprasījumi darbojas; uzskaitītie rīki ir atkarīgi no lietotāja tiesībām. Klientiem ar 2025. gada protokola versiju jāsūta `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC atbilde', + mcpOverrideAccess: 'Izlaist piekļuves pārbaudes. Tikai izstrādei.', + mcpGet: 'Netiek atbalstīts: serveris neatver notikumu straumi', + errorPluginMcpGet405: 'Metode nav atļauta, izmantojiet POST', + mcpProtocolVersion: 'Saskaņotā MCP protokola versija, piem., `2025-06-18`.', + mcpResult202: 'Pieņemts: ķermenī bija tikai paziņojumi vai atbildes', + errorPluginMcp404: 'Nezināma MCP metode', + errorPluginMcp406: 'Galvenē `Accept` trūkst `application/json` vai `text/event-stream`', + errorPluginMcp413: 'Pieprasījuma ķermenis ir pārāk liels', + errorPluginMcp415: '`Content-Type` jābūt `application/json`', + seoTitle: 'Ģenerēt meta virsrakstu', + seoDescription: 'Ģenerēt meta aprakstu', + seoUrl: 'Ģenerēt priekšskatījuma URL', + seoImage: 'Ģenerēt meta attēlu', + seoBody: + 'Rediģējamais dokuments: `collectionSlug` vai `globalSlug`, tā `id` un pašreizējie `doc` dati. Tiek nodots jūsu ģenerēšanas funkcijai.', + seoResult: 'Ģenerētā vērtība. Tukša virkne, ja ģenerēšanas funkcija nav iestatīta.', + searchReindex: 'Pārbūvēt meklēšanas indeksu dažām kolekcijām', + searchReindexResult: 'Pārindeksēšanas kopsavilkums', + tenantOptions: 'Uzskaitīt īrniekus, kurus lietotājs var izvēlēties', + tenantOptionsResult: 'Īrnieku opcijas', + exportDownload: 'Eksportēt dokumentus failā', + exportDownloadResult: 'Eksporta fails', + exportPreview: 'Eksporta priekšskatījums', + importPreview: 'Importa faila priekšskatījums', + previewResult: 'Priekšskatījuma dokumentu lapa', + importFileData: 'Faila saturs, kodēts base64.', + r2Upload: 'Augšupielādēt failu R2 pa daļām', + r2UploadDesc: + 'Trīs soļi vienā maršrutā. Sākums: nosūtiet `collection`, `fileName` un `fileType`. Katra daļa: pievienojiet `multipartId`, `multipartKey`, `multipartNumber` un `signedReceipt` un nosūtiet baitus. Pabeigšana: tas pats bez `multipartNumber`, ar daļu JSON sarakstu.', + r2UploadResult: 'Augšupielāde sākta, daļa augšupielādēta vai augšupielāde pabeigta (objekta atslēga kā teksts)', + errorStorageR2412: 'Fails ar šo atslēgu jau pastāv', localizationHeading: 'Lokalizācija', localizationNote: diff --git a/src/shared/translations/locales/my.ts b/src/shared/translations/locales/my.ts index 0377768..4ba86f2 100644 --- a/src/shared/translations/locales/my.ts +++ b/src/shared/translations/locales/my.ts @@ -85,11 +85,26 @@ export const my: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-stringified {{schema}} အကွက်များ။ ဥပမာ: `{"alt":"A caption"}`။', fileServe: 'ဖိုင်', filePartial: '`Range` request အတွက် ဖိုင်၏ အစိတ်အပိုင်း', + paramFileVersion: 'ဤဗားရှင်း ID ဖြင့် သိမ်းထားသော ဖိုင်ကို ပြန်ပေးသည်။', uploadInstructionsSummary: 'စာရွက်စာတမ်းကို မသိမ်းမီ ဖိုင်အပ်လုဒ်လုပ်ရန် ညွှန်ကြားချက်များ ရယူသည်', uploadInstructionsResult: 'ဖိုင် bytes ပို့ရမည့်နေရာနှင့် create သို့မဟုတ် update request နှင့်အတူ ပို့ရမည့် `file` တန်ဖိုး', uploadStagePutSummary: 'ယာယီအပ်လုဒ်အတွက် ဖိုင် bytes ပို့သည်', uploadStageDeleteSummary: 'ယာယီအပ်လုဒ်ကို ဖျက်သည်', uploadStageResult: 'ပြီးပါပြီ၊ အကြောင်းအရာ မရှိပါ', + paramPreferenceKey: 'Preference ၏ key၊ ဥပမာ `nav`။', + preferenceGet: 'လက်ရှိအသုံးပြုသူ၏ preference တစ်ခုကို ရယူရန်', + preferenceGetResult: 'သိမ်းထားသော preference၊ မရှိပါက `value: null`', + preferenceSet: 'လက်ရှိအသုံးပြုသူ၏ preference တစ်ခုကို သိမ်းရန်', + preferenceSetBody: 'တန်ဖိုးကို `value` ဖြင့် ပို့ပါ။ `value` မပါသော body ကို တစ်ခုလုံး တန်ဖိုးအဖြစ် သိမ်းသည်။', + preferenceSetResult: 'သိမ်းထားသော preference', + preferenceDelete: 'လက်ရှိအသုံးပြုသူ၏ preference တစ်ခုကို ဖျက်ရန်', + preferenceDeleteResult: 'ဖျက်လိုက်သော preference', + reorder: 'စီစဉ်နိုင်သော စာရင်းတွင် စာရွက်စာတမ်းများကို ရွှေ့ရန်', + reorderBody: + '`newKeyWillBe: "greater"` ဖြစ်ပါက `docsToMove` ကို `target` ၏ နောက်သို့၊ `"less"` ဖြစ်ပါက ရှေ့သို့ ရွှေ့သည်။ `orderableFieldName` သည် စီစဉ်နိုင်သော collection အတွက် `_order` ဖြစ်ပြီး စီစဉ်နိုင်သော join အတွက် ၎င်း၏ order field ဖြစ်သည်။ `target.key` မပါပါက Payload သည် order key မရှိသေးသော စာရွက်စာတမ်းများကိုသာ order key ပေးသည်။', + reorderResult: + 'Order key အသစ်များ၊ သို့မဟုတ် Payload က order key များကို ပထမဆုံးအကြိမ် သတ်မှတ်သောအခါ `message: "initial migration"`', + errorReorder400: 'Request body မမှန်ကန်ပါ၊ အကြောင်းရင်းကို `error` တွင် ကြည့်ပါ', error400: 'အတည်ပြုမှု သို့မဟုတ် query အမှား (ValidationError, QueryError)', error401: 'အထောက်အထား မစိစစ်ရသေးပါ (AuthenticationError)', @@ -113,6 +128,72 @@ export const my: PluginDefaultTranslationsObject = { tagJobs: 'Jobs', tagUploads: 'Uploads', tagAccess: 'Access', + tagReorder: 'Reorder', + tagPlugins: 'ပလပ်ဂင်များ', + tagPluginsDesc: 'တရားဝင် Payload plugin များက ထည့်ပေးသော endpoint များ။', + errorPlugin400: 'မှားယွင်းသော request', + errorPlugin401: 'authentication မပြုလုပ်ရသေးပါ', + errorPlugin403: 'ခွင့်မပြုပါ', + errorPlugin404: 'ရှာမတွေ့ပါ', + errorPlugin500: 'server အတွင်းပိုင်း အမှား', + ecommerceAddItem: 'ခြင်းတောင်းထဲသို့ ပစ္စည်းထည့်ရန်', + ecommerceRemoveItem: 'ခြင်းတောင်းမှ ပစ္စည်းဖယ်ရန်', + ecommerceUpdateItem: 'ခြင်းတောင်းရှိ ပစ္စည်းအရေအတွက် ပြောင်းရန်', + ecommerceClearCart: 'ခြင်းတောင်းထဲရှိ ပစ္စည်းအားလုံး ဖယ်ရန်', + ecommerceMergeCart: 'ဧည့်သည် ခြင်းတောင်းကို ဤခြင်းတောင်းနှင့် ပေါင်းရန်', + ecommerceCartAccess: 'ခြင်းတောင်းပိုင်ရှင် သို့မဟုတ် body ထဲတွင် `secret` ပါသော ဧည့်သည် ခြင်းတောင်းအတွက် ခွင့်ပြုသည်။', + ecommerceCartResult: 'အပ်ဒိတ်လုပ်ပြီး ခြင်းတောင်း', + errorPluginEcommerce404: 'ခြင်းတောင်း ရှာမတွေ့ပါ သို့မဟုတ် ဝင်ရောက်၍ မရပါ', + ecommerceQuantity: 'အရေအတွက်အသစ်၊ သို့မဟုတ် n ဖြင့် ပြောင်းရန် `{ "$inc": n }`။', + ecommerceInitiatePayment: '`{{method}}` ဖြင့် ငွေပေးချေမှု စတင်ရန်', + ecommerceConfirmOrder: '`{{method}}` ဖြင့် ငွေပေးချေမှုကို အတည်ပြုပြီး order ဖန်တီးရန်', + ecommercePaymentBody: + '`cartID` (ဧည့်သည် ခြင်းတောင်းအတွက် `secret` နှင့်) သို့မဟုတ် user ၏ ခြင်းတောင်းကို သုံးသည်။ user မရှိပါက `customerEmail` လိုအပ်သည်။ payment adapter က field ထပ်လိုနိုင်သည်။', + ecommerceInitiateResult: 'ငွေပေးချေမှု စတင်ပြီး။ adapter က client secret ကဲ့သို့ ကိုယ်ပိုင် field များ ထည့်သည်။', + ecommerceConfirmResult: 'order ဖန်တီးပြီးပါပြီ', + stripeWebhook: 'Stripe webhook event များ လက်ခံရန်', + stripeWebhookBody: + '`Stripe-Signature` header တွင် လက်မှတ်ထိုးထားသော မူရင်း Stripe event။ API client များမဟုတ်ဘဲ Stripe က ခေါ်သည်။', + stripeWebhookResult: 'event လက်ခံရရှိပြီး', + errorPluginStripeWebhook400: 'လက်မှတ် စစ်ဆေးမှု မအောင်မြင်ပါ', + stripeRest: 'ခွင့်ပြုထားသော Stripe API method ကို ခေါ်ရန်', + stripeRestResult: 'Stripe API ရလဒ်', + errorPluginStripeRest404: 'Stripe API က အမှားပြန်ပေးသည်', + mcp: 'MCP JSON-RPC message ပို့ရန်', + mcpDesc: + 'Streamable HTTP ပေါ်ရှိ Model Context Protocol၊ JSON ဖြင့် တုံ့ပြန်သည်။ အမည်မသိ request များ အလုပ်လုပ်သည်။ ပြသသော tool များမှာ user ၏ access ပေါ် မူတည်သည်။ 2025 protocol ဗားရှင်းသုံးသော client များသည် `Accept: application/json, text/event-stream` ကို ပို့ရမည်။', + mcpResult: 'JSON-RPC တုံ့ပြန်ချက်', + mcpOverrideAccess: 'access စစ်ဆေးမှုများ ကျော်ရန်။ development အတွက်သာ။', + mcpGet: 'မပံ့ပိုးပါ: server သည် event stream မဖွင့်ပါ', + errorPluginMcpGet405: 'method ကို ခွင့်မပြုပါ၊ POST ကို သုံးပါ', + mcpProtocolVersion: 'သဘောတူထားသော MCP protocol ဗားရှင်း၊ ဥပမာ `2025-06-18`။', + mcpResult202: 'လက်ခံပြီး: body တွင် notification သို့မဟုတ် response များသာ ပါသည်', + errorPluginMcp404: 'မသိသော MCP method', + errorPluginMcp406: '`Accept` header တွင် `application/json` သို့မဟုတ် `text/event-stream` မပါပါ', + errorPluginMcp413: 'request body အရွယ်အစား ကြီးလွန်းသည်', + errorPluginMcp415: '`Content-Type` သည် `application/json` ဖြစ်ရမည်', + seoTitle: 'meta title ထုတ်ရန်', + seoDescription: 'meta description ထုတ်ရန်', + seoUrl: 'preview URL ထုတ်ရန်', + seoImage: 'meta image ထုတ်ရန်', + seoBody: + 'ပြင်ဆင်နေသော document: `collectionSlug` သို့မဟုတ် `globalSlug`၊ ၎င်း၏ `id` နှင့် လက်ရှိ `doc` data။ သင်၏ generate function သို့ ပေးပို့သည်။', + seoResult: 'ထုတ်ပေးသော တန်ဖိုး။ generate function မသတ်မှတ်ထားပါက string အလွတ်။', + searchReindex: 'collection အချို့အတွက် search index ကို ပြန်တည်ဆောက်ရန်', + searchReindexResult: 'reindex အနှစ်ချုပ်', + tenantOptions: 'user ရွေးနိုင်သော tenant များ စာရင်း', + tenantOptionsResult: 'tenant ရွေးချယ်စရာများ', + exportDownload: 'document များကို file အဖြစ် export လုပ်ရန်', + exportDownloadResult: 'export ဖိုင်', + exportPreview: 'export ကို preview ကြည့်ရန်', + importPreview: 'import file ကို preview ကြည့်ရန်', + previewResult: 'preview document စာမျက်နှာတစ်ခု', + importFileData: 'file အကြောင်းအရာ၊ base64 ဖြင့် encode လုပ်ထားသည်။', + r2Upload: 'file ကို R2 သို့ အပိုင်းလိုက် upload လုပ်ရန်', + r2UploadDesc: + 'route တစ်ခုတည်းတွင် အဆင့်သုံးဆင့်။ စတင်: `collection`၊ `fileName` နှင့် `fileType` ပို့ပါ။ အပိုင်းတိုင်း: `multipartId`၊ `multipartKey`၊ `multipartNumber` နှင့် `signedReceipt` ထည့်ပြီး byte များ ပို့ပါ။ ပြီးဆုံး: `multipartNumber` မပါဘဲ အလားတူ၊ အပိုင်းများ၏ JSON စာရင်းနှင့်။', + r2UploadResult: 'upload စတင်ပြီး၊ အပိုင်း upload ပြီး သို့မဟုတ် upload ပြီးဆုံး (object key ကို text အဖြစ်)', + errorStorageR2412: 'ဤ key တွင် file ရှိပြီးသားဖြစ်သည်', localizationHeading: 'Localization', localizationNote: diff --git a/src/shared/translations/locales/nb.ts b/src/shared/translations/locales/nb.ts index b4fd28a..8c27392 100644 --- a/src/shared/translations/locales/nb.ts +++ b/src/shared/translations/locales/nb.ts @@ -88,12 +88,27 @@ export const nb: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-strengkodede {{schema}}-felt. Eksempel: `{"alt":"A caption"}`.', fileServe: 'Filen', filePartial: 'En del av filen ved en `Range`-forespørsel', + paramFileVersion: 'Returnerer filen som er lagret med denne versjons-ID-en.', uploadInstructionsSummary: 'Hent instruksjoner for å laste opp en fil før dokumentet lagres', uploadInstructionsResult: 'Hvor filens byte skal sendes, og `file`-verdien som skal sendes med opprett- eller oppdater-forespørselen', uploadStagePutSummary: 'Send filens byte for en midlertidig opplasting', uploadStageDeleteSummary: 'Slett en midlertidig opplasting', uploadStageResult: 'Ferdig, uten innhold', + paramPreferenceKey: 'Nøkkelen til innstillingen, f.eks. `nav`.', + preferenceGet: 'Hent en innstilling for gjeldende bruker', + preferenceGetResult: 'Den lagrede innstillingen, eller `value: null` hvis den ikke finnes', + preferenceSet: 'Lagre en innstilling for gjeldende bruker', + preferenceSetBody: 'Send verdien som `value`. En body uten `value` lagres som selve verdien.', + preferenceSetResult: 'Den lagrede innstillingen', + preferenceDelete: 'Slett en innstilling for gjeldende bruker', + preferenceDeleteResult: 'Den slettede innstillingen', + reorder: 'Flytt dokumenter i en sorterbar liste', + reorderBody: + 'Flytter `docsToMove` etter `target` med `newKeyWillBe: "greater"`, eller foran med `"less"`. `orderableFieldName` er `_order` for en sorterbar samling, eller rekkefølgefeltet til en sorterbar join. Uten `target.key` gir Payload bare rekkefølgenøkler til dokumenter som mangler dem.', + reorderResult: + 'De nye rekkefølgenøklene, eller `message: "initial migration"` når Payload satte rekkefølgenøklene for første gang', + errorReorder400: 'Ugyldig forespørselsbody; årsaken står i `error`', error400: 'Validerings- eller spørringsfeil (ValidationError, QueryError)', error401: 'Ikke autentisert (AuthenticationError)', @@ -117,6 +132,72 @@ export const nb: PluginDefaultTranslationsObject = { tagJobs: 'Jobber', tagUploads: 'Opplastinger', tagAccess: 'Tilgang', + tagReorder: 'Rekkefølge', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endepunkter lagt til av offisielle Payload-plugins.', + errorPlugin400: 'Ugyldig forespørsel', + errorPlugin401: 'Ikke autentisert', + errorPlugin403: 'Forbudt', + errorPlugin404: 'Ikke funnet', + errorPlugin500: 'Intern serverfeil', + ecommerceAddItem: 'Legg en vare i handlekurven', + ecommerceRemoveItem: 'Fjern en vare fra handlekurven', + ecommerceUpdateItem: 'Endre antallet for en vare i handlekurven', + ecommerceClearCart: 'Fjern alle varer fra handlekurven', + ecommerceMergeCart: 'Slå sammen en gjestekurv med denne handlekurven', + ecommerceCartAccess: 'Tillatt for eieren av handlekurven, eller for en gjestekurv med sin `secret` i body.', + ecommerceCartResult: 'Den oppdaterte handlekurven', + errorPluginEcommerce404: 'Handlekurven ble ikke funnet eller er ikke tilgjengelig', + ecommerceQuantity: 'Nytt antall, eller `{ "$inc": n }` for å endre det med n.', + ecommerceInitiatePayment: 'Start en betaling med `{{method}}`', + ecommerceConfirmOrder: 'Bekreft betalingen med `{{method}}` og opprett ordren', + ecommercePaymentBody: + 'Bruker `cartID` (med `secret` for en gjestekurv) eller brukerens handlekurv. Uten bruker er `customerEmail` påkrevd. Betalingsadapteren kan kreve flere felt.', + ecommerceInitiateResult: 'Betaling startet. Adapteren legger til egne felt, f.eks. en client secret.', + ecommerceConfirmResult: 'Ordren ble opprettet', + stripeWebhook: 'Motta Stripe-webhookhendelser', + stripeWebhookBody: + 'Den rå Stripe-hendelsen, signert i headeren `Stripe-Signature`. Kalles av Stripe, ikke av API-klienter.', + stripeWebhookResult: 'Hendelse mottatt', + errorPluginStripeWebhook400: 'Signaturkontrollen mislyktes', + stripeRest: 'Kall en tillatt Stripe API-metode', + stripeRestResult: 'Resultatet fra Stripe API', + errorPluginStripeRest404: 'Stripe API returnerte en feil', + mcp: 'Send en MCP JSON-RPC-melding', + mcpDesc: + 'Model Context Protocol over Streamable HTTP med JSON-svar. Anonyme forespørsler fungerer; verktøyene som vises, avhenger av brukerens tilgang. Klienter med en protokollversjon fra 2025 må sende `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC-svar', + mcpOverrideAccess: 'Hopp over tilgangskontroller. Kun for utvikling.', + mcpGet: 'Ikke støttet: serveren åpner ingen hendelsesstrøm', + errorPluginMcpGet405: 'Metode ikke tillatt, bruk POST', + mcpProtocolVersion: 'Avtalt MCP-protokollversjon, f.eks. `2025-06-18`.', + mcpResult202: 'Akseptert: body inneholdt bare varsler eller svar', + errorPluginMcp404: 'Ukjent MCP-metode', + errorPluginMcp406: 'Headeren `Accept` mangler `application/json` eller `text/event-stream`', + errorPluginMcp413: 'Forespørselens body er for stor', + errorPluginMcp415: '`Content-Type` må være `application/json`', + seoTitle: 'Generer metatittelen', + seoDescription: 'Generer metabeskrivelsen', + seoUrl: 'Generer forhåndsvisnings-URL-en', + seoImage: 'Generer metabildet', + seoBody: + 'Dokumentet som redigeres: `collectionSlug` eller `globalSlug`, dets `id` og gjeldende `doc`-data. Sendes videre til generate-funksjonen din.', + seoResult: 'Den genererte verdien. En tom streng når ingen generate-funksjon er satt.', + searchReindex: 'Bygg søkeindeksen på nytt for enkelte samlinger', + searchReindexResult: 'Sammendrag av reindeksering', + tenantOptions: 'Vis leietakerne brukeren kan velge', + tenantOptionsResult: 'Leietakervalg', + exportDownload: 'Eksporter dokumenter til en fil', + exportDownloadResult: 'Eksportfilen', + exportPreview: 'Forhåndsvis en eksport', + importPreview: 'Forhåndsvis en importfil', + previewResult: 'En side med forhåndsvisningsdokumenter', + importFileData: 'Filinnhold, base64-kodet.', + r2Upload: 'Last opp en fil til R2 i deler', + r2UploadDesc: + 'Tre trinn på én rute. Start: send `collection`, `fileName` og `fileType`. Hver del: legg til `multipartId`, `multipartKey`, `multipartNumber` og `signedReceipt`, og send bytene. Fullfør: det samme uten `multipartNumber`, med JSON-listen over deler.', + r2UploadResult: 'Opplasting startet, del lastet opp eller opplasting fullført (objektnøkkelen som tekst)', + errorStorageR2412: 'Det finnes allerede en fil med denne nøkkelen', localizationHeading: 'Lokalisering', localizationNote: diff --git a/src/shared/translations/locales/nl.ts b/src/shared/translations/locales/nl.ts index 4284d17..fa9c91c 100644 --- a/src/shared/translations/locales/nl.ts +++ b/src/shared/translations/locales/nl.ts @@ -90,12 +90,28 @@ export const nl: PluginDefaultTranslationsObject = { uploadPayloadField: '{{schema}}-velden als JSON-string. Voorbeeld: `{"alt":"A caption"}`.', fileServe: 'Het bestand', filePartial: 'Een deel van het bestand, voor een `Range`-verzoek', + paramFileVersion: 'Geeft het bestand terug dat met deze versie-ID is opgeslagen.', uploadInstructionsSummary: 'Instructies ophalen om een bestand te uploaden voordat het document wordt opgeslagen', uploadInstructionsResult: 'Waar de bytes van het bestand naartoe moeten, en de `file`-waarde die met het aanmaak- of bijwerkverzoek mee moet', uploadStagePutSummary: 'De bytes van het bestand verzenden voor een tijdelijke upload', uploadStageDeleteSummary: 'Een tijdelijke upload verwijderen', uploadStageResult: 'Klaar, geen inhoud', + paramPreferenceKey: 'De sleutel van de voorkeur, bijv. `nav`.', + preferenceGet: 'Een voorkeur van de huidige gebruiker ophalen', + preferenceGetResult: 'De opgeslagen voorkeur, of `value: null` als die er niet is', + preferenceSet: 'Een voorkeur van de huidige gebruiker opslaan', + preferenceSetBody: + 'Stuur de waarde als `value`. Een body zonder `value` wordt in zijn geheel als waarde opgeslagen.', + preferenceSetResult: 'De opgeslagen voorkeur', + preferenceDelete: 'Een voorkeur van de huidige gebruiker verwijderen', + preferenceDeleteResult: 'De verwijderde voorkeur', + reorder: 'Documenten verplaatsen in een sorteerbare lijst', + reorderBody: + 'Verplaatst `docsToMove` na `target` met `newKeyWillBe: "greater"`, of ervoor met `"less"`. `orderableFieldName` is `_order` voor een sorteerbare collectie, of het volgordeveld van een sorteerbare join. Zonder `target.key` geeft Payload alleen volgordesleutels aan documenten die er nog geen hebben.', + reorderResult: + 'De nieuwe volgordesleutels, of `message: "initial migration"` wanneer Payload de volgordesleutels voor het eerst heeft gezet', + errorReorder400: 'Ongeldige request-body; de reden staat in `error`', error400: 'Validatie- of queryfout (ValidationError, QueryError)', error401: 'Niet geverifieerd (AuthenticationError)', @@ -119,6 +135,73 @@ export const nl: PluginDefaultTranslationsObject = { tagJobs: 'Taken', tagUploads: 'Uploads', tagAccess: 'Toegang', + tagReorder: 'Volgorde', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endpoints die officiële Payload-plugins toevoegen.', + errorPlugin400: 'Ongeldig verzoek', + errorPlugin401: 'Niet geauthenticeerd', + errorPlugin403: 'Verboden', + errorPlugin404: 'Niet gevonden', + errorPlugin500: 'Interne serverfout', + ecommerceAddItem: 'Een artikel aan de winkelwagen toevoegen', + ecommerceRemoveItem: 'Een artikel uit de winkelwagen verwijderen', + ecommerceUpdateItem: 'De hoeveelheid van een winkelwagenartikel wijzigen', + ecommerceClearCart: 'Alle artikelen uit de winkelwagen verwijderen', + ecommerceMergeCart: 'Een gastwinkelwagen samenvoegen met deze winkelwagen', + ecommerceCartAccess: + 'Toegestaan voor de eigenaar van de winkelwagen, of voor een gastwinkelwagen met zijn `secret` in de body.', + ecommerceCartResult: 'De bijgewerkte winkelwagen', + errorPluginEcommerce404: 'Winkelwagen niet gevonden of niet toegankelijk', + ecommerceQuantity: 'Nieuwe hoeveelheid, of `{ "$inc": n }` om die met n te wijzigen.', + ecommerceInitiatePayment: 'Een betaling starten met `{{method}}`', + ecommerceConfirmOrder: 'De betaling met `{{method}}` bevestigen en de bestelling aanmaken', + ecommercePaymentBody: + 'Gebruikt `cartID` (met `secret` voor een gastwinkelwagen) of de winkelwagen van de gebruiker. Zonder gebruiker is `customerEmail` verplicht. De betaaladapter kan meer velden nodig hebben.', + ecommerceInitiateResult: 'Betaling gestart. De adapter voegt eigen velden toe, bijv. een client secret.', + ecommerceConfirmResult: 'De bestelling is aangemaakt', + stripeWebhook: 'Stripe-webhookevents ontvangen', + stripeWebhookBody: + 'Het ruwe Stripe-event, ondertekend in de header `Stripe-Signature`. Wordt door Stripe aangeroepen, niet door API-clients.', + stripeWebhookResult: 'Event ontvangen', + errorPluginStripeWebhook400: 'De handtekeningcontrole is mislukt', + stripeRest: 'Een toegestane Stripe API-methode aanroepen', + stripeRestResult: 'Het resultaat van de Stripe API', + errorPluginStripeRest404: 'De Stripe API gaf een fout terug', + mcp: 'Een MCP JSON-RPC-bericht versturen', + mcpDesc: + 'Model Context Protocol via Streamable HTTP, met JSON-antwoorden. Anonieme verzoeken werken; welke tools worden getoond, hangt af van de rechten van de gebruiker. Clients met een protocolversie uit 2025 moeten `Accept: application/json, text/event-stream` meesturen.', + mcpResult: 'JSON-RPC-antwoord', + mcpOverrideAccess: 'Toegangscontroles overslaan. Alleen voor ontwikkeling.', + mcpGet: 'Niet ondersteund: de server opent geen eventstream', + errorPluginMcpGet405: 'Methode niet toegestaan, gebruik POST', + mcpProtocolVersion: 'Overeengekomen MCP-protocolversie, bijv. `2025-06-18`.', + mcpResult202: 'Geaccepteerd: de body bevatte alleen notificaties of antwoorden', + errorPluginMcp404: 'Onbekende MCP-methode', + errorPluginMcp406: 'In de header `Accept` ontbreekt `application/json` of `text/event-stream`', + errorPluginMcp413: 'De body van het verzoek is te groot', + errorPluginMcp415: '`Content-Type` moet `application/json` zijn', + seoTitle: 'De metatitel genereren', + seoDescription: 'De metabeschrijving genereren', + seoUrl: 'De preview-URL genereren', + seoImage: 'De meta-afbeelding genereren', + seoBody: + 'Het document dat wordt bewerkt: `collectionSlug` of `globalSlug`, zijn `id` en de huidige `doc`-gegevens. Wordt doorgegeven aan je generate-functie.', + seoResult: 'De gegenereerde waarde. Een lege string als er geen generate-functie is ingesteld.', + searchReindex: 'De zoekindex voor enkele collecties opnieuw opbouwen', + searchReindexResult: 'Samenvatting van de herindexering', + tenantOptions: 'De tenants tonen die de gebruiker kan kiezen', + tenantOptionsResult: 'Tenantopties', + exportDownload: 'Documenten naar een bestand exporteren', + exportDownloadResult: 'Het exportbestand', + exportPreview: 'Voorbeeld van een export', + importPreview: 'Voorbeeld van een importbestand', + previewResult: 'Een pagina met voorbeelddocumenten', + importFileData: 'Bestandsinhoud, base64-gecodeerd.', + r2Upload: 'Een bestand in delen naar R2 uploaden', + r2UploadDesc: + 'Drie stappen op één route. Start: stuur `collection`, `fileName` en `fileType`. Elk deel: voeg `multipartId`, `multipartKey`, `multipartNumber` en `signedReceipt` toe en stuur de bytes. Afronden: hetzelfde zonder `multipartNumber`, met de JSON-lijst van delen.', + r2UploadResult: 'Upload gestart, deel geüpload of upload voltooid (de objectsleutel als tekst)', + errorStorageR2412: 'Er bestaat al een bestand met deze sleutel', localizationHeading: 'Lokalisatie', localizationNote: diff --git a/src/shared/translations/locales/pl.ts b/src/shared/translations/locales/pl.ts index 6ceda1f..9d617d2 100644 --- a/src/shared/translations/locales/pl.ts +++ b/src/shared/translations/locales/pl.ts @@ -88,12 +88,27 @@ export const pl: PluginDefaultTranslationsObject = { uploadPayloadField: 'Pola {{schema}} w postaci ciągu JSON. Przykład: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Plik', filePartial: 'Część pliku dla żądania `Range`', + paramFileVersion: 'Zwraca plik zapisany z tym ID wersji.', uploadInstructionsSummary: 'Pobierz instrukcje przesłania pliku przed zapisaniem dokumentu', uploadInstructionsResult: 'Dokąd wysłać bajty pliku i jaką wartość `file` wysłać z żądaniem utworzenia lub aktualizacji', uploadStagePutSummary: 'Wyślij bajty pliku do tymczasowego przesyłania', uploadStageDeleteSummary: 'Usuń tymczasowe przesyłanie', uploadStageResult: 'Gotowe, bez treści', + paramPreferenceKey: 'Klucz preferencji, np. `nav`.', + preferenceGet: 'Pobierz preferencję bieżącego użytkownika', + preferenceGetResult: 'Zapisana preferencja lub `value: null`, gdy jej brak', + preferenceSet: 'Zapisz preferencję bieżącego użytkownika', + preferenceSetBody: 'Wyślij wartość w `value`. Treść bez `value` jest zapisywana w całości jako wartość.', + preferenceSetResult: 'Zapisana preferencja', + preferenceDelete: 'Usuń preferencję bieżącego użytkownika', + preferenceDeleteResult: 'Usunięta preferencja', + reorder: 'Przenieś dokumenty na liście z kolejnością', + reorderBody: + 'Przenosi `docsToMove` za `target` przy `newKeyWillBe: "greater"` lub przed niego przy `"less"`. `orderableFieldName` to `_order` dla kolekcji z kolejnością lub pole kolejności dla joina z kolejnością. Bez `target.key` Payload tylko nadaje klucze kolejności dokumentom, które ich nie mają.', + reorderResult: + 'Nowe klucze kolejności lub `message: "initial migration"`, gdy Payload po raz pierwszy nadał klucze kolejności', + errorReorder400: 'Nieprawidłowa treść żądania; powód jest w `error`', error400: 'Błąd walidacji lub zapytania (ValidationError, QueryError)', error401: 'Brak uwierzytelnienia (AuthenticationError)', @@ -117,6 +132,72 @@ export const pl: PluginDefaultTranslationsObject = { tagJobs: 'Zadania', tagUploads: 'Przesyłanie plików', tagAccess: 'Dostęp', + tagReorder: 'Kolejność', + tagPlugins: 'Wtyczki', + tagPluginsDesc: 'Endpointy dodane przez oficjalne wtyczki Payload.', + errorPlugin400: 'Nieprawidłowe żądanie', + errorPlugin401: 'Nie uwierzytelniono', + errorPlugin403: 'Zabronione', + errorPlugin404: 'Nie znaleziono', + errorPlugin500: 'Wewnętrzny błąd serwera', + ecommerceAddItem: 'Dodaj produkt do koszyka', + ecommerceRemoveItem: 'Usuń produkt z koszyka', + ecommerceUpdateItem: 'Zmień ilość produktu w koszyku', + ecommerceClearCart: 'Usuń wszystkie produkty z koszyka', + ecommerceMergeCart: 'Scal koszyk gościa z tym koszykiem', + ecommerceCartAccess: 'Dozwolone dla właściciela koszyka lub dla koszyka gościa z jego `secret` w treści żądania.', + ecommerceCartResult: 'Zaktualizowany koszyk', + errorPluginEcommerce404: 'Nie znaleziono koszyka lub jest niedostępny', + ecommerceQuantity: 'Nowa ilość lub `{ "$inc": n }`, aby zmienić ją o n.', + ecommerceInitiatePayment: 'Rozpocznij płatność przez `{{method}}`', + ecommerceConfirmOrder: 'Potwierdź płatność przez `{{method}}` i utwórz zamówienie', + ecommercePaymentBody: + 'Używa `cartID` (z `secret` dla koszyka gościa) lub koszyka użytkownika. Bez użytkownika `customerEmail` jest wymagany. Adapter płatności może wymagać dodatkowych pól.', + ecommerceInitiateResult: 'Płatność rozpoczęta. Adapter dodaje własne pola, np. client secret.', + ecommerceConfirmResult: 'Zamówienie zostało utworzone', + stripeWebhook: 'Odbieraj zdarzenia webhooka Stripe', + stripeWebhookBody: + 'Surowe zdarzenie Stripe podpisane w nagłówku `Stripe-Signature`. Wywoływane przez Stripe, nie przez klientów API.', + stripeWebhookResult: 'Zdarzenie odebrane', + errorPluginStripeWebhook400: 'Weryfikacja podpisu nie powiodła się', + stripeRest: 'Wywołaj dozwoloną metodę API Stripe', + stripeRestResult: 'Wynik API Stripe', + errorPluginStripeRest404: 'API Stripe zwróciło błąd', + mcp: 'Wyślij wiadomość MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol przez Streamable HTTP, z odpowiedziami JSON. Żądania anonimowe działają; lista narzędzi zależy od uprawnień użytkownika. Klienci z wersją protokołu z 2025 roku muszą wysyłać `Accept: application/json, text/event-stream`.', + mcpResult: 'Odpowiedź JSON-RPC', + mcpOverrideAccess: 'Pomiń kontrole dostępu. Tylko do programowania.', + mcpGet: 'Nieobsługiwane: serwer nie otwiera strumienia zdarzeń', + errorPluginMcpGet405: 'Metoda niedozwolona, użyj POST', + mcpProtocolVersion: 'Wynegocjowana wersja protokołu MCP, np. `2025-06-18`.', + mcpResult202: 'Przyjęto: treść zawierała tylko powiadomienia lub odpowiedzi', + errorPluginMcp404: 'Nieznana metoda MCP', + errorPluginMcp406: 'W nagłówku `Accept` brakuje `application/json` lub `text/event-stream`', + errorPluginMcp413: 'Treść żądania jest za duża', + errorPluginMcp415: '`Content-Type` musi być `application/json`', + seoTitle: 'Wygeneruj meta tytuł', + seoDescription: 'Wygeneruj meta opis', + seoUrl: 'Wygeneruj URL podglądu', + seoImage: 'Wygeneruj meta obraz', + seoBody: + 'Edytowany dokument: `collectionSlug` lub `globalSlug`, jego `id` i bieżące dane `doc`. Przekazywany do Twojej funkcji generującej.', + seoResult: 'Wygenerowana wartość. Pusty ciąg, gdy nie ustawiono funkcji generującej.', + searchReindex: 'Przebuduj indeks wyszukiwania dla wybranych kolekcji', + searchReindexResult: 'Podsumowanie reindeksacji', + tenantOptions: 'Lista najemców, których użytkownik może wybrać', + tenantOptionsResult: 'Opcje najemców', + exportDownload: 'Eksportuj dokumenty do pliku', + exportDownloadResult: 'Plik eksportu', + exportPreview: 'Podgląd eksportu', + importPreview: 'Podgląd pliku importu', + previewResult: 'Strona dokumentów podglądu', + importFileData: 'Zawartość pliku zakodowana w base64.', + r2Upload: 'Prześlij plik do R2 w częściach', + r2UploadDesc: + 'Trzy kroki na jednej trasie. Start: wyślij `collection`, `fileName` i `fileType`. Każda część: dodaj `multipartId`, `multipartKey`, `multipartNumber` i `signedReceipt` i wyślij bajty. Zakończenie: to samo bez `multipartNumber`, z listą części w JSON.', + r2UploadResult: 'Przesyłanie rozpoczęte, część przesłana lub przesyłanie zakończone (klucz obiektu jako tekst)', + errorStorageR2412: 'Plik o tym kluczu już istnieje', localizationHeading: 'Lokalizacja', localizationNote: diff --git a/src/shared/translations/locales/pt.ts b/src/shared/translations/locales/pt.ts index 3e41e64..9d2c067 100644 --- a/src/shared/translations/locales/pt.ts +++ b/src/shared/translations/locales/pt.ts @@ -89,12 +89,27 @@ export const pt: PluginDefaultTranslationsObject = { uploadPayloadField: 'Campos {{schema}} em JSON stringificado. Exemplo: `{"alt":"A caption"}`.', fileServe: 'O arquivo', filePartial: 'Uma parte do arquivo, para uma requisição `Range`', + paramFileVersion: 'Retorna o arquivo salvo com este ID de versão.', uploadInstructionsSummary: 'Obter instruções para enviar um arquivo antes de salvar o documento', uploadInstructionsResult: 'Para onde enviar os bytes do arquivo e o valor `file` a enviar com a requisição de criação ou atualização', uploadStagePutSummary: 'Enviar os bytes do arquivo para um upload temporário', uploadStageDeleteSummary: 'Excluir um upload temporário', uploadStageResult: 'Concluído, sem conteúdo', + paramPreferenceKey: 'A chave da preferência, ex.: `nav`.', + preferenceGet: 'Obter uma preferência do usuário atual', + preferenceGetResult: 'A preferência salva, ou `value: null` quando não existe', + preferenceSet: 'Salvar uma preferência do usuário atual', + preferenceSetBody: 'Envie o valor em `value`. Um corpo sem `value` é salvo como o próprio valor.', + preferenceSetResult: 'A preferência salva', + preferenceDelete: 'Excluir uma preferência do usuário atual', + preferenceDeleteResult: 'A preferência excluída', + reorder: 'Mover documentos em uma lista ordenável', + reorderBody: + 'Move `docsToMove` para depois de `target` com `newKeyWillBe: "greater"`, ou para antes com `"less"`. `orderableFieldName` é `_order` para uma coleção ordenável, ou o campo de ordem de um join ordenável. Sem `target.key`, o Payload só atribui chaves de ordem aos documentos que não têm.', + reorderResult: + 'As novas chaves de ordem, ou `message: "initial migration"` quando o Payload definiu as chaves de ordem pela primeira vez', + errorReorder400: 'Corpo da requisição inválido; o motivo está em `error`', error400: 'Erro de validação ou de consulta (ValidationError, QueryError)', error401: 'Não autenticado (AuthenticationError)', @@ -118,6 +133,73 @@ export const pt: PluginDefaultTranslationsObject = { tagJobs: 'Tarefas', tagUploads: 'Uploads', tagAccess: 'Acesso', + tagReorder: 'Reordenação', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Endpoints adicionados pelos plugins oficiais do Payload.', + errorPlugin400: 'Requisição inválida', + errorPlugin401: 'Não autenticado', + errorPlugin403: 'Proibido', + errorPlugin404: 'Não encontrado', + errorPlugin500: 'Erro interno do servidor', + ecommerceAddItem: 'Adicionar um item ao carrinho', + ecommerceRemoveItem: 'Remover um item do carrinho', + ecommerceUpdateItem: 'Alterar a quantidade de um item do carrinho', + ecommerceClearCart: 'Remover todos os itens do carrinho', + ecommerceMergeCart: 'Mesclar um carrinho de convidado neste carrinho', + ecommerceCartAccess: + 'Permitido para o dono do carrinho, ou para um carrinho de convidado com o seu `secret` no corpo.', + ecommerceCartResult: 'O carrinho atualizado', + errorPluginEcommerce404: 'Carrinho não encontrado ou inacessível', + ecommerceQuantity: 'Nova quantidade, ou `{ "$inc": n }` para alterá-la em n.', + ecommerceInitiatePayment: 'Iniciar um pagamento com `{{method}}`', + ecommerceConfirmOrder: 'Confirmar o pagamento com `{{method}}` e criar o pedido', + ecommercePaymentBody: + 'Usa `cartID` (com `secret` para um carrinho de convidado) ou o carrinho do usuário. Sem usuário, `customerEmail` é obrigatório. O adaptador de pagamento pode exigir mais campos.', + ecommerceInitiateResult: 'Pagamento iniciado. O adaptador adiciona seus próprios campos, p. ex. um client secret.', + ecommerceConfirmResult: 'O pedido foi criado', + stripeWebhook: 'Receber eventos de webhook do Stripe', + stripeWebhookBody: + 'O evento bruto do Stripe, assinado no cabeçalho `Stripe-Signature`. Chamado pelo Stripe, não por clientes da API.', + stripeWebhookResult: 'Evento recebido', + errorPluginStripeWebhook400: 'A verificação da assinatura falhou', + stripeRest: 'Chamar um método permitido da API do Stripe', + stripeRestResult: 'O resultado da API do Stripe', + errorPluginStripeRest404: 'A API do Stripe retornou um erro', + mcp: 'Enviar uma mensagem MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol sobre Streamable HTTP, com respostas JSON. Requisições anônimas funcionam; as ferramentas listadas dependem das permissões do usuário. Clientes com uma versão do protocolo de 2025 devem enviar `Accept: application/json, text/event-stream`.', + mcpResult: 'Resposta JSON-RPC', + mcpOverrideAccess: 'Ignorar as verificações de acesso. Somente para desenvolvimento.', + mcpGet: 'Não suportado: o servidor não abre nenhum fluxo de eventos', + errorPluginMcpGet405: 'Método não permitido, use POST', + mcpProtocolVersion: 'Versão negociada do protocolo MCP, p. ex. `2025-06-18`.', + mcpResult202: 'Aceito: o corpo continha apenas notificações ou respostas', + errorPluginMcp404: 'Método MCP desconhecido', + errorPluginMcp406: 'Falta `application/json` ou `text/event-stream` no cabeçalho `Accept`', + errorPluginMcp413: 'O corpo da requisição é grande demais', + errorPluginMcp415: '`Content-Type` deve ser `application/json`', + seoTitle: 'Gerar o meta título', + seoDescription: 'Gerar a meta descrição', + seoUrl: 'Gerar a URL de pré-visualização', + seoImage: 'Gerar a meta imagem', + seoBody: + 'O documento em edição: `collectionSlug` ou `globalSlug`, seu `id` e os dados atuais de `doc`. Repassado à sua função de geração.', + seoResult: 'O valor gerado. Uma string vazia quando nenhuma função de geração está definida.', + searchReindex: 'Reconstruir o índice de busca de algumas coleções', + searchReindexResult: 'Resumo da reindexação', + tenantOptions: 'Listar os tenants que o usuário pode escolher', + tenantOptionsResult: 'Opções de tenant', + exportDownload: 'Exportar documentos para um arquivo', + exportDownloadResult: 'O arquivo exportado', + exportPreview: 'Pré-visualizar uma exportação', + importPreview: 'Pré-visualizar um arquivo de importação', + previewResult: 'Uma página de documentos de pré-visualização', + importFileData: 'Conteúdo do arquivo, codificado em base64.', + r2Upload: 'Enviar um arquivo ao R2 em partes', + r2UploadDesc: + 'Três etapas numa só rota. Início: envie `collection`, `fileName` e `fileType`. Cada parte: adicione `multipartId`, `multipartKey`, `multipartNumber` e `signedReceipt`, e envie os bytes. Conclusão: o mesmo sem `multipartNumber`, com a lista JSON das partes.', + r2UploadResult: 'Upload iniciado, parte enviada ou upload concluído (a chave do objeto como texto)', + errorStorageR2412: 'Já existe um arquivo nesta chave', localizationHeading: 'Localização', localizationNote: diff --git a/src/shared/translations/locales/ro.ts b/src/shared/translations/locales/ro.ts index 2a1e53e..ddd6d65 100644 --- a/src/shared/translations/locales/ro.ts +++ b/src/shared/translations/locales/ro.ts @@ -89,12 +89,27 @@ export const ro: PluginDefaultTranslationsObject = { uploadPayloadField: 'Câmpuri {{schema}} serializate în JSON. Exemplu: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Fișierul', filePartial: 'O parte din fișier, pentru o cerere `Range`', + paramFileVersion: 'Returnează fișierul salvat cu acest ID de versiune.', uploadInstructionsSummary: 'Obține instrucțiuni pentru încărcarea unui fișier înainte de salvarea documentului', uploadInstructionsResult: 'Unde se trimit octeții fișierului și valoarea `file` de trimis cu cererea de creare sau actualizare', uploadStagePutSummary: 'Trimite octeții fișierului pentru o încărcare temporară', uploadStageDeleteSummary: 'Șterge o încărcare temporară', uploadStageResult: 'Gata, fără conținut', + paramPreferenceKey: 'Cheia preferinței, de ex. `nav`.', + preferenceGet: 'Obține o preferință a utilizatorului curent', + preferenceGetResult: 'Preferința salvată sau `value: null` dacă nu există', + preferenceSet: 'Salvează o preferință a utilizatorului curent', + preferenceSetBody: 'Trimiteți valoarea în `value`. Un corp fără `value` este salvat integral ca valoare.', + preferenceSetResult: 'Preferința salvată', + preferenceDelete: 'Șterge o preferință a utilizatorului curent', + preferenceDeleteResult: 'Preferința ștearsă', + reorder: 'Mută documente într-o listă ordonabilă', + reorderBody: + 'Mută `docsToMove` după `target` cu `newKeyWillBe: "greater"` sau înainte cu `"less"`. `orderableFieldName` este `_order` pentru o colecție ordonabilă sau câmpul de ordine al unui join ordonabil. Fără `target.key`, Payload doar atribuie chei de ordine documentelor care nu au.', + reorderResult: + 'Noile chei de ordine sau `message: "initial migration"` când Payload a setat cheile de ordine prima dată', + errorReorder400: 'Corp de cerere invalid; motivul este în `error`', error400: 'Eroare de validare sau de interogare (ValidationError, QueryError)', error401: 'Neautentificat (AuthenticationError)', @@ -118,6 +133,72 @@ export const ro: PluginDefaultTranslationsObject = { tagJobs: 'Joburi', tagUploads: 'Încărcări', tagAccess: 'Acces', + tagReorder: 'Reordonare', + tagPlugins: 'Pluginuri', + tagPluginsDesc: 'Endpoint-uri adăugate de pluginurile oficiale Payload.', + errorPlugin400: 'Cerere invalidă', + errorPlugin401: 'Neautentificat', + errorPlugin403: 'Interzis', + errorPlugin404: 'Negăsit', + errorPlugin500: 'Eroare internă de server', + ecommerceAddItem: 'Adaugă un produs în coș', + ecommerceRemoveItem: 'Elimină un produs din coș', + ecommerceUpdateItem: 'Modifică cantitatea unui produs din coș', + ecommerceClearCart: 'Elimină toate produsele din coș', + ecommerceMergeCart: 'Unește un coș de vizitator cu acest coș', + ecommerceCartAccess: 'Permis proprietarului coșului sau pentru un coș de vizitator cu `secret` în corp.', + ecommerceCartResult: 'Coșul actualizat', + errorPluginEcommerce404: 'Coș negăsit sau inaccesibil', + ecommerceQuantity: 'Cantitatea nouă sau `{ "$inc": n }` pentru a o modifica cu n.', + ecommerceInitiatePayment: 'Inițiază o plată cu `{{method}}`', + ecommerceConfirmOrder: 'Confirmă plata cu `{{method}}` și creează comanda', + ecommercePaymentBody: + 'Folosește `cartID` (cu `secret` pentru un coș de vizitator) sau coșul utilizatorului. Fără utilizator, `customerEmail` este obligatoriu. Adaptorul de plată poate cere mai multe câmpuri.', + ecommerceInitiateResult: 'Plată inițiată. Adaptorul adaugă propriile câmpuri, de ex. un client secret.', + ecommerceConfirmResult: 'Comanda a fost creată', + stripeWebhook: 'Primește evenimente webhook Stripe', + stripeWebhookBody: + 'Evenimentul Stripe brut, semnat în antetul `Stripe-Signature`. Apelat de Stripe, nu de clienții API.', + stripeWebhookResult: 'Eveniment primit', + errorPluginStripeWebhook400: 'Verificarea semnăturii a eșuat', + stripeRest: 'Apelează o metodă permisă din API-ul Stripe', + stripeRestResult: 'Rezultatul API-ului Stripe', + errorPluginStripeRest404: 'API-ul Stripe a returnat o eroare', + mcp: 'Trimite un mesaj MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol prin Streamable HTTP, cu răspunsuri JSON. Cererile anonime funcționează; instrumentele listate depind de drepturile utilizatorului. Clienții cu o versiune de protocol din 2025 trebuie să trimită `Accept: application/json, text/event-stream`.', + mcpResult: 'Răspuns JSON-RPC', + mcpOverrideAccess: 'Omite verificările de acces. Doar pentru dezvoltare.', + mcpGet: 'Nesuportat: serverul nu deschide niciun flux de evenimente', + errorPluginMcpGet405: 'Metodă nepermisă, folosește POST', + mcpProtocolVersion: 'Versiunea negociată a protocolului MCP, de ex. `2025-06-18`.', + mcpResult202: 'Acceptat: corpul conținea doar notificări sau răspunsuri', + errorPluginMcp404: 'Metodă MCP necunoscută', + errorPluginMcp406: 'Antetului `Accept` îi lipsește `application/json` sau `text/event-stream`', + errorPluginMcp413: 'Corpul cererii este prea mare', + errorPluginMcp415: '`Content-Type` trebuie să fie `application/json`', + seoTitle: 'Generează meta titlul', + seoDescription: 'Generează meta descrierea', + seoUrl: 'Generează URL-ul de previzualizare', + seoImage: 'Generează meta imaginea', + seoBody: + 'Documentul editat: `collectionSlug` sau `globalSlug`, `id`-ul său și datele `doc` curente. Transmis funcției tale de generare.', + seoResult: 'Valoarea generată. Un șir gol când nu este setată nicio funcție de generare.', + searchReindex: 'Reconstruiește indexul de căutare pentru unele colecții', + searchReindexResult: 'Rezumatul reindexării', + tenantOptions: 'Listează tenanții pe care utilizatorul îi poate alege', + tenantOptionsResult: 'Opțiuni de tenant', + exportDownload: 'Exportă documente într-un fișier', + exportDownloadResult: 'Fișierul exportat', + exportPreview: 'Previzualizează un export', + importPreview: 'Previzualizează un fișier de import', + previewResult: 'O pagină de documente de previzualizare', + importFileData: 'Conținutul fișierului, codificat base64.', + r2Upload: 'Încarcă un fișier în R2 pe părți', + r2UploadDesc: + 'Trei pași pe o singură rută. Start: trimite `collection`, `fileName` și `fileType`. Fiecare parte: adaugă `multipartId`, `multipartKey`, `multipartNumber` și `signedReceipt` și trimite octeții. Final: la fel, fără `multipartNumber`, cu lista JSON a părților.', + r2UploadResult: 'Încărcare pornită, parte încărcată sau încărcare finalizată (cheia obiectului ca text)', + errorStorageR2412: 'Există deja un fișier la această cheie', localizationHeading: 'Localizare', localizationNote: diff --git a/src/shared/translations/locales/rs.ts b/src/shared/translations/locales/rs.ts index 3de82bd..b80d4ed 100644 --- a/src/shared/translations/locales/rs.ts +++ b/src/shared/translations/locales/rs.ts @@ -87,12 +87,27 @@ export const rs: PluginDefaultTranslationsObject = { uploadPayloadField: 'Поља {{schema}} у JSON стринг формату. Пример: `{"alt":"A caption"}`.', fileServe: 'Датотека', filePartial: 'Део датотеке за `Range` захтев', + paramFileVersion: 'Враћа датотеку сачувану са овим ID-јем верзије.', uploadInstructionsSummary: 'Преузимање упутстава за отпремање датотеке пре чувања документа', uploadInstructionsResult: 'Где послати бајтове датотеке и вредност `file` која се шаље уз захтев за креирање или ажурирање', uploadStagePutSummary: 'Слање бајтова датотеке за привремено отпремање', uploadStageDeleteSummary: 'Брисање привременог отпремања', uploadStageResult: 'Готово, без садржаја', + paramPreferenceKey: 'Кључ подешавања, нпр. `nav`.', + preferenceGet: 'Преузми подешавање тренутног корисника', + preferenceGetResult: 'Сачувано подешавање или `value: null` ако не постоји', + preferenceSet: 'Сачувај подешавање тренутног корисника', + preferenceSetBody: 'Пошаљите вредност у `value`. Тело без `value` чува се у целости као вредност.', + preferenceSetResult: 'Сачувано подешавање', + preferenceDelete: 'Обриши подешавање тренутног корисника', + preferenceDeleteResult: 'Обрисано подешавање', + reorder: 'Премести документе на листи са редоследом', + reorderBody: + 'Премешта `docsToMove` иза `target` уз `newKeyWillBe: "greater"` или испред њега уз `"less"`. `orderableFieldName` је `_order` за колекцију са редоследом или поље редоследа за join са редоследом. Без `target.key` Payload само додељује кључеве редоследа документима који их немају.', + reorderResult: + 'Нови кључеви редоследа или `message: "initial migration"` када је Payload први пут поставио кључеве редоследа', + errorReorder400: 'Неважеће тело захтева; разлог је у `error`', error400: 'Грешка валидације или упита (ValidationError, QueryError)', error401: 'Није аутентификован (AuthenticationError)', @@ -116,6 +131,72 @@ export const rs: PluginDefaultTranslationsObject = { tagJobs: 'Послови', tagUploads: 'Отпремања', tagAccess: 'Приступ', + tagReorder: 'Редослед', + tagPlugins: 'Додаци', + tagPluginsDesc: 'Крајње тачке које додају званични Payload додаци.', + errorPlugin400: 'Неисправан захтев', + errorPlugin401: 'Нисте пријављени', + errorPlugin403: 'Забрањено', + errorPlugin404: 'Није пронађено', + errorPlugin500: 'Интерна грешка сервера', + ecommerceAddItem: 'Додај артикал у корпу', + ecommerceRemoveItem: 'Уклони артикал из корпе', + ecommerceUpdateItem: 'Промени количину артикла у корпи', + ecommerceClearCart: 'Уклони све артикле из корпе', + ecommerceMergeCart: 'Споји корпу госта са овом корпом', + ecommerceCartAccess: 'Дозвољено власнику корпе или за корпу госта са њеним `secret` у телу захтева.', + ecommerceCartResult: 'Ажурирана корпа', + errorPluginEcommerce404: 'Корпа није пронађена или није доступна', + ecommerceQuantity: 'Нова количина или `{ "$inc": n }` за промену за n.', + ecommerceInitiatePayment: 'Покрени плаћање преко `{{method}}`', + ecommerceConfirmOrder: 'Потврди плаћање преко `{{method}}` и направи поруџбину', + ecommercePaymentBody: + 'Користи `cartID` (са `secret` за корпу госта) или корпу корисника. Без корисника `customerEmail` је обавезан. Адаптеру за плаћање могу требати још поља.', + ecommerceInitiateResult: 'Плаћање је покренуто. Адаптер додаје своја поља, нпр. client secret.', + ecommerceConfirmResult: 'Поруџбина је направљена', + stripeWebhook: 'Прими Stripe webhook догађаје', + stripeWebhookBody: + 'Сирови Stripe догађај, потписан у заглављу `Stripe-Signature`. Позива га Stripe, не API клијенти.', + stripeWebhookResult: 'Догађај је примљен', + errorPluginStripeWebhook400: 'Провера потписа није успела', + stripeRest: 'Позови дозвољену методу Stripe API-ја', + stripeRestResult: 'Резултат Stripe API-ја', + errorPluginStripeRest404: 'Stripe API је вратио грешку', + mcp: 'Пошаљи MCP JSON-RPC поруку', + mcpDesc: + 'Model Context Protocol преко Streamable HTTP-а, са JSON одговорима. Анонимни захтеви раде; наведени алати зависе од права корисника. Клијенти са верзијом протокола из 2025. морају да шаљу `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC одговор', + mcpOverrideAccess: 'Прескочи провере приступа. Само за развој.', + mcpGet: 'Није подржано: сервер не отвара ток догађаја', + errorPluginMcpGet405: 'Метода није дозвољена, користите POST', + mcpProtocolVersion: 'Договорена верзија MCP протокола, нпр. `2025-06-18`.', + mcpResult202: 'Прихваћено: тело је садржало само обавештења или одговоре', + errorPluginMcp404: 'Непозната MCP метода', + errorPluginMcp406: 'Заглављу `Accept` недостаје `application/json` или `text/event-stream`', + errorPluginMcp413: 'Тело захтева је превелико', + errorPluginMcp415: '`Content-Type` мора бити `application/json`', + seoTitle: 'Генериши мета наслов', + seoDescription: 'Генериши мета опис', + seoUrl: 'Генериши URL за преглед', + seoImage: 'Генериши мета слику', + seoBody: + 'Документ који се уређује: `collectionSlug` или `globalSlug`, његов `id` и тренутни подаци `doc`. Прослеђује се вашој функцији за генерисање.', + seoResult: 'Генерисана вредност. Празан стринг ако функција за генерисање није подешена.', + searchReindex: 'Поново изгради индекс претраге за неке колекције', + searchReindexResult: 'Сажетак поновног индексирања', + tenantOptions: 'Наведи закупце које корисник може да изабере', + tenantOptionsResult: 'Опције закупаца', + exportDownload: 'Извези документе у фајл', + exportDownloadResult: 'Фајл извоза', + exportPreview: 'Преглед извоза', + importPreview: 'Преглед фајла за увоз', + previewResult: 'Страница докумената за преглед', + importFileData: 'Садржај фајла, кодиран у base64.', + r2Upload: 'Отпреми фајл у R2 у деловима', + r2UploadDesc: + 'Три корака на једној рути. Почетак: пошаљите `collection`, `fileName` и `fileType`. Сваки део: додајте `multipartId`, `multipartKey`, `multipartNumber` и `signedReceipt` и пошаљите бајтове. Крај: исто без `multipartNumber`, са JSON листом делова.', + r2UploadResult: 'Отпремање започето, део отпремљен или отпремање завршено (кључ објекта као текст)', + errorStorageR2412: 'Фајл са овим кључем већ постоји', localizationHeading: 'Локализација', localizationNote: diff --git a/src/shared/translations/locales/rsLatin.ts b/src/shared/translations/locales/rsLatin.ts index a6684a9..9fe9ba8 100644 --- a/src/shared/translations/locales/rsLatin.ts +++ b/src/shared/translations/locales/rsLatin.ts @@ -90,12 +90,27 @@ export const rsLatin: PluginDefaultTranslationsObject = { uploadPayloadField: '{{schema}} polja pretvorena u JSON string. Primer: `{"alt":"A caption"}`.', fileServe: 'Datoteka', filePartial: 'Deo datoteke za `Range` zahtev', + paramFileVersion: 'Vraća datoteku sačuvanu sa ovim ID-jem verzije.', uploadInstructionsSummary: 'Preuzimanje uputstava za otpremanje datoteke pre čuvanja dokumenta', uploadInstructionsResult: 'Gde poslati bajtove datoteke i vrednost `file` koja se šalje uz zahtev za kreiranje ili ažuriranje', uploadStagePutSummary: 'Slanje bajtova datoteke za privremeno otpremanje', uploadStageDeleteSummary: 'Brisanje privremenog otpremanja', uploadStageResult: 'Gotovo, bez sadržaja', + paramPreferenceKey: 'Ključ podešavanja, npr. `nav`.', + preferenceGet: 'Preuzmi podešavanje trenutnog korisnika', + preferenceGetResult: 'Sačuvano podešavanje ili `value: null` ako ne postoji', + preferenceSet: 'Sačuvaj podešavanje trenutnog korisnika', + preferenceSetBody: 'Pošaljite vrednost u `value`. Telo bez `value` čuva se u celosti kao vrednost.', + preferenceSetResult: 'Sačuvano podešavanje', + preferenceDelete: 'Obriši podešavanje trenutnog korisnika', + preferenceDeleteResult: 'Obrisano podešavanje', + reorder: 'Premesti dokumente na listi sa redosledom', + reorderBody: + 'Premešta `docsToMove` iza `target` uz `newKeyWillBe: "greater"` ili ispred njega uz `"less"`. `orderableFieldName` je `_order` za kolekciju sa redosledom ili polje redosleda za join sa redosledom. Bez `target.key` Payload samo dodeljuje ključeve redosleda dokumentima koji ih nemaju.', + reorderResult: + 'Novi ključevi redosleda ili `message: "initial migration"` kada je Payload prvi put postavio ključeve redosleda', + errorReorder400: 'Nevažeće telo zahteva; razlog je u `error`', error400: 'Greška u validaciji ili upitu (ValidationError, QueryError)', error401: 'Niste autentifikovani (AuthenticationError)', @@ -119,6 +134,72 @@ export const rsLatin: PluginDefaultTranslationsObject = { tagJobs: 'Poslovi', tagUploads: 'Otpremanja', tagAccess: 'Pristup', + tagReorder: 'Redosled', + tagPlugins: 'Dodaci', + tagPluginsDesc: 'Krajnje tačke koje dodaju zvanični Payload dodaci.', + errorPlugin400: 'Neispravan zahtev', + errorPlugin401: 'Niste prijavljeni', + errorPlugin403: 'Zabranjeno', + errorPlugin404: 'Nije pronađeno', + errorPlugin500: 'Interna greška servera', + ecommerceAddItem: 'Dodaj artikal u korpu', + ecommerceRemoveItem: 'Ukloni artikal iz korpe', + ecommerceUpdateItem: 'Promeni količinu artikla u korpi', + ecommerceClearCart: 'Ukloni sve artikle iz korpe', + ecommerceMergeCart: 'Spoji korpu gosta sa ovom korpom', + ecommerceCartAccess: 'Dozvoljeno vlasniku korpe ili za korpu gosta sa njenim `secret` u telu zahteva.', + ecommerceCartResult: 'Ažurirana korpa', + errorPluginEcommerce404: 'Korpa nije pronađena ili nije dostupna', + ecommerceQuantity: 'Nova količina ili `{ "$inc": n }` za promenu za n.', + ecommerceInitiatePayment: 'Pokreni plaćanje preko `{{method}}`', + ecommerceConfirmOrder: 'Potvrdi plaćanje preko `{{method}}` i napravi porudžbinu', + ecommercePaymentBody: + 'Koristi `cartID` (sa `secret` za korpu gosta) ili korpu korisnika. Bez korisnika `customerEmail` je obavezan. Adapteru za plaćanje mogu trebati još polja.', + ecommerceInitiateResult: 'Plaćanje je pokrenuto. Adapter dodaje svoja polja, npr. client secret.', + ecommerceConfirmResult: 'Porudžbina je napravljena', + stripeWebhook: 'Primi Stripe webhook događaje', + stripeWebhookBody: + 'Sirovi Stripe događaj, potpisan u zaglavlju `Stripe-Signature`. Poziva ga Stripe, ne API klijenti.', + stripeWebhookResult: 'Događaj je primljen', + errorPluginStripeWebhook400: 'Provera potpisa nije uspela', + stripeRest: 'Pozovi dozvoljenu metodu Stripe API-ja', + stripeRestResult: 'Rezultat Stripe API-ja', + errorPluginStripeRest404: 'Stripe API je vratio grešku', + mcp: 'Pošalji MCP JSON-RPC poruku', + mcpDesc: + 'Model Context Protocol preko Streamable HTTP-a, sa JSON odgovorima. Anonimni zahtevi rade; navedeni alati zavise od prava korisnika. Klijenti sa verzijom protokola iz 2025. moraju da šalju `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC odgovor', + mcpOverrideAccess: 'Preskoči provere pristupa. Samo za razvoj.', + mcpGet: 'Nije podržano: server ne otvara tok događaja', + errorPluginMcpGet405: 'Metoda nije dozvoljena, koristite POST', + mcpProtocolVersion: 'Dogovorena verzija MCP protokola, npr. `2025-06-18`.', + mcpResult202: 'Prihvaćeno: telo je sadržalo samo obaveštenja ili odgovore', + errorPluginMcp404: 'Nepoznata MCP metoda', + errorPluginMcp406: 'Zaglavlju `Accept` nedostaje `application/json` ili `text/event-stream`', + errorPluginMcp413: 'Telo zahteva je preveliko', + errorPluginMcp415: '`Content-Type` mora biti `application/json`', + seoTitle: 'Generiši meta naslov', + seoDescription: 'Generiši meta opis', + seoUrl: 'Generiši URL za pregled', + seoImage: 'Generiši meta sliku', + seoBody: + 'Dokument koji se uređuje: `collectionSlug` ili `globalSlug`, njegov `id` i trenutni podaci `doc`. Prosleđuje se vašoj funkciji za generisanje.', + seoResult: 'Generisana vrednost. Prazan string ako funkcija za generisanje nije podešena.', + searchReindex: 'Ponovo izgradi indeks pretrage za neke kolekcije', + searchReindexResult: 'Sažetak ponovnog indeksiranja', + tenantOptions: 'Navedi zakupce koje korisnik može da izabere', + tenantOptionsResult: 'Opcije zakupaca', + exportDownload: 'Izvezi dokumente u fajl', + exportDownloadResult: 'Fajl izvoza', + exportPreview: 'Pregled izvoza', + importPreview: 'Pregled fajla za uvoz', + previewResult: 'Stranica dokumenata za pregled', + importFileData: 'Sadržaj fajla, kodiran u base64.', + r2Upload: 'Otpremi fajl u R2 u delovima', + r2UploadDesc: + 'Tri koraka na jednoj ruti. Početak: pošaljite `collection`, `fileName` i `fileType`. Svaki deo: dodajte `multipartId`, `multipartKey`, `multipartNumber` i `signedReceipt` i pošaljite bajtove. Kraj: isto bez `multipartNumber`, sa JSON listom delova.', + r2UploadResult: 'Otpremanje započeto, deo otpremljen ili otpremanje završeno (ključ objekta kao tekst)', + errorStorageR2412: 'Fajl sa ovim ključem već postoji', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/shared/translations/locales/ru.ts b/src/shared/translations/locales/ru.ts index 8919dbb..09626a6 100644 --- a/src/shared/translations/locales/ru.ts +++ b/src/shared/translations/locales/ru.ts @@ -86,12 +86,27 @@ export const ru: PluginDefaultTranslationsObject = { uploadPayloadField: 'Поля {{schema}} в виде JSON-строки. Пример: `{"alt":"A caption"}`.', fileServe: 'Файл', filePartial: 'Часть файла для запроса с `Range`', + paramFileVersion: 'Отдаёт файл, сохранённый с этим ID версии.', uploadInstructionsSummary: 'Получить инструкции для загрузки файла до сохранения документа', uploadInstructionsResult: 'Куда отправить байты файла и какое значение `file` передать в запросе на создание или обновление', uploadStagePutSummary: 'Отправить байты файла для временной загрузки', uploadStageDeleteSummary: 'Удалить временную загрузку', uploadStageResult: 'Готово, без содержимого', + paramPreferenceKey: 'Ключ настройки, например `nav`.', + preferenceGet: 'Получить настройку текущего пользователя', + preferenceGetResult: 'Сохранённая настройка или `value: null`, если её нет', + preferenceSet: 'Сохранить настройку текущего пользователя', + preferenceSetBody: 'Передайте значение в `value`. Тело без `value` сохраняется целиком как значение.', + preferenceSetResult: 'Сохранённая настройка', + preferenceDelete: 'Удалить настройку текущего пользователя', + preferenceDeleteResult: 'Удалённая настройка', + reorder: 'Переместить документы в упорядочиваемом списке', + reorderBody: + 'Перемещает `docsToMove` после `target` при `newKeyWillBe: "greater"` или перед ним при `"less"`. `orderableFieldName` равно `_order` для упорядочиваемой коллекции или полю порядка упорядочиваемого join. Без `target.key` Payload только назначает ключи порядка документам, у которых их нет.', + reorderResult: + 'Новые ключи порядка или `message: "initial migration"`, когда Payload впервые назначил ключи порядка', + errorReorder400: 'Некорректное тело запроса; причина указана в `error`', error400: 'Ошибка валидации или запроса (ValidationError, QueryError)', error401: 'Не аутентифицирован (AuthenticationError)', @@ -115,6 +130,72 @@ export const ru: PluginDefaultTranslationsObject = { tagJobs: 'Задачи', tagUploads: 'Загрузки', tagAccess: 'Доступ', + tagReorder: 'Порядок', + tagPlugins: 'Плагины', + tagPluginsDesc: 'Эндпоинты официальных плагинов Payload.', + errorPlugin400: 'Некорректный запрос', + errorPlugin401: 'Не авторизован', + errorPlugin403: 'Доступ запрещён', + errorPlugin404: 'Не найдено', + errorPlugin500: 'Внутренняя ошибка сервера', + ecommerceAddItem: 'Добавить товар в корзину', + ecommerceRemoveItem: 'Удалить товар из корзины', + ecommerceUpdateItem: 'Изменить количество товара в корзине', + ecommerceClearCart: 'Удалить все товары из корзины', + ecommerceMergeCart: 'Объединить гостевую корзину с этой корзиной', + ecommerceCartAccess: 'Доступно владельцу корзины или для гостевой корзины с её `secret` в теле запроса.', + ecommerceCartResult: 'Обновлённая корзина', + errorPluginEcommerce404: 'Корзина не найдена или недоступна', + ecommerceQuantity: 'Новое количество или `{ "$inc": n }`, чтобы изменить его на n.', + ecommerceInitiatePayment: 'Начать оплату через `{{method}}`', + ecommerceConfirmOrder: 'Подтвердить оплату через `{{method}}` и создать заказ', + ecommercePaymentBody: + 'Использует `cartID` (с `secret` для гостевой корзины) или корзину пользователя. Без пользователя обязателен `customerEmail`. Платёжному адаптеру могут понадобиться другие поля.', + ecommerceInitiateResult: 'Оплата начата. Адаптер добавляет свои поля, например client secret.', + ecommerceConfirmResult: 'Заказ создан', + stripeWebhook: 'Принять события вебхука Stripe', + stripeWebhookBody: + 'Исходное событие Stripe, подписанное в заголовке `Stripe-Signature`. Вызывается Stripe, а не клиентами API.', + stripeWebhookResult: 'Событие получено', + errorPluginStripeWebhook400: 'Проверка подписи не прошла', + stripeRest: 'Вызвать разрешённый метод API Stripe', + stripeRestResult: 'Результат API Stripe', + errorPluginStripeRest404: 'API Stripe вернул ошибку', + mcp: 'Отправить сообщение MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol поверх Streamable HTTP с JSON-ответами. Анонимные запросы работают; список инструментов зависит от прав пользователя. Клиенты с версией протокола 2025 года должны отправлять `Accept: application/json, text/event-stream`.', + mcpResult: 'Ответ JSON-RPC', + mcpOverrideAccess: 'Пропустить проверки доступа. Только для разработки.', + mcpGet: 'Не поддерживается: сервер не открывает поток событий', + errorPluginMcpGet405: 'Метод не разрешён, используйте POST', + mcpProtocolVersion: 'Согласованная версия протокола MCP, например `2025-06-18`.', + mcpResult202: 'Принято: в теле были только уведомления или ответы', + errorPluginMcp404: 'Неизвестный метод MCP', + errorPluginMcp406: 'В заголовке `Accept` нет `application/json` или `text/event-stream`', + errorPluginMcp413: 'Тело запроса слишком большое', + errorPluginMcp415: '`Content-Type` должен быть `application/json`', + seoTitle: 'Сгенерировать мета-заголовок', + seoDescription: 'Сгенерировать мета-описание', + seoUrl: 'Сгенерировать URL предпросмотра', + seoImage: 'Сгенерировать мета-изображение', + seoBody: + 'Редактируемый документ: `collectionSlug` или `globalSlug`, его `id` и текущие данные `doc`. Передаётся в вашу функцию генерации.', + seoResult: 'Сгенерированное значение. Пустая строка, если функция генерации не задана.', + searchReindex: 'Перестроить поисковый индекс для выбранных коллекций', + searchReindexResult: 'Итог переиндексации', + tenantOptions: 'Список арендаторов, доступных пользователю для выбора', + tenantOptionsResult: 'Варианты арендаторов', + exportDownload: 'Экспортировать документы в файл', + exportDownloadResult: 'Файл экспорта', + exportPreview: 'Предпросмотр экспорта', + importPreview: 'Предпросмотр файла импорта', + previewResult: 'Страница документов предпросмотра', + importFileData: 'Содержимое файла в кодировке base64.', + r2Upload: 'Загрузить файл в R2 по частям', + r2UploadDesc: + 'Три шага на одном маршруте. Начало: передайте `collection`, `fileName` и `fileType`. Каждая часть: добавьте `multipartId`, `multipartKey`, `multipartNumber` и `signedReceipt` и отправьте байты. Завершение: то же без `multipartNumber`, со списком частей в JSON.', + r2UploadResult: 'Загрузка начата, часть загружена или загрузка завершена (ключ объекта текстом)', + errorStorageR2412: 'Файл с этим ключом уже существует', localizationHeading: 'Локализация', localizationNote: diff --git a/src/shared/translations/locales/sk.ts b/src/shared/translations/locales/sk.ts index 5ddd3f3..c64e646 100644 --- a/src/shared/translations/locales/sk.ts +++ b/src/shared/translations/locales/sk.ts @@ -90,12 +90,27 @@ export const sk: PluginDefaultTranslationsObject = { uploadPayloadField: 'Polia {{schema}} serializované do JSON. Príklad: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Súbor', filePartial: 'Časť súboru pre požiadavku `Range`', + paramFileVersion: 'Vráti súbor uložený s týmto ID verzie.', uploadInstructionsSummary: 'Získať pokyny na nahranie súboru pred uložením dokumentu', uploadInstructionsResult: 'Kam poslať bajty súboru a hodnota `file`, ktorá sa pošle s požiadavkou na vytvorenie alebo úpravu', uploadStagePutSummary: 'Odoslať bajty súboru pre dočasné nahranie', uploadStageDeleteSummary: 'Odstrániť dočasné nahranie', uploadStageResult: 'Hotovo, bez obsahu', + paramPreferenceKey: 'Kľúč predvoľby, napr. `nav`.', + preferenceGet: 'Získať predvoľbu aktuálneho používateľa', + preferenceGetResult: 'Uložená predvoľba alebo `value: null`, ak neexistuje', + preferenceSet: 'Uložiť predvoľbu aktuálneho používateľa', + preferenceSetBody: 'Pošlite hodnotu v `value`. Telo bez `value` sa uloží celé ako hodnota.', + preferenceSetResult: 'Uložená predvoľba', + preferenceDelete: 'Odstrániť predvoľbu aktuálneho používateľa', + preferenceDeleteResult: 'Odstránená predvoľba', + reorder: 'Presunúť dokumenty v zoraditeľnom zozname', + reorderBody: + 'Presunie `docsToMove` za `target` s `newKeyWillBe: "greater"` alebo pred neho s `"less"`. `orderableFieldName` je `_order` pri zoraditeľnej kolekcii alebo pole poradia zoraditeľného joinu. Bez `target.key` Payload iba pridelí kľúče poradia dokumentom, ktoré ich nemajú.', + reorderResult: + 'Nové kľúče poradia alebo `message: "initial migration"`, keď Payload prvýkrát nastavil kľúče poradia', + errorReorder400: 'Neplatné telo požiadavky; dôvod je v `error`', error400: 'Chyba validácie alebo dotazu (ValidationError, QueryError)', error401: 'Neprihlásený (AuthenticationError)', @@ -119,6 +134,72 @@ export const sk: PluginDefaultTranslationsObject = { tagJobs: 'Úlohy', tagUploads: 'Nahrávanie', tagAccess: 'Prístup', + tagReorder: 'Poradie', + tagPlugins: 'Pluginy', + tagPluginsDesc: 'Endpointy pridané oficiálnymi pluginmi Payloadu.', + errorPlugin400: 'Neplatná požiadavka', + errorPlugin401: 'Neprihlásený', + errorPlugin403: 'Zakázané', + errorPlugin404: 'Nenájdené', + errorPlugin500: 'Interná chyba servera', + ecommerceAddItem: 'Pridať položku do košíka', + ecommerceRemoveItem: 'Odobrať položku z košíka', + ecommerceUpdateItem: 'Zmeniť množstvo položky v košíku', + ecommerceClearCart: 'Odobrať všetky položky z košíka', + ecommerceMergeCart: 'Zlúčiť košík hosťa s týmto košíkom', + ecommerceCartAccess: 'Povolené vlastníkovi košíka alebo pre košík hosťa s jeho `secret` v tele požiadavky.', + ecommerceCartResult: 'Aktualizovaný košík', + errorPluginEcommerce404: 'Košík sa nenašiel alebo nie je prístupný', + ecommerceQuantity: 'Nové množstvo alebo `{ "$inc": n }` na zmenu o n.', + ecommerceInitiatePayment: 'Spustiť platbu cez `{{method}}`', + ecommerceConfirmOrder: 'Potvrdiť platbu cez `{{method}}` a vytvoriť objednávku', + ecommercePaymentBody: + 'Použije `cartID` (so `secret` pre košík hosťa) alebo košík používateľa. Bez používateľa je `customerEmail` povinný. Platobný adaptér môže vyžadovať ďalšie polia.', + ecommerceInitiateResult: 'Platba spustená. Adaptér pridáva vlastné polia, napr. client secret.', + ecommerceConfirmResult: 'Objednávka bola vytvorená', + stripeWebhook: 'Prijímať udalosti webhooku Stripe', + stripeWebhookBody: + 'Nespracovaná udalosť Stripe podpísaná v hlavičke `Stripe-Signature`. Volá ju Stripe, nie klienti API.', + stripeWebhookResult: 'Udalosť prijatá', + errorPluginStripeWebhook400: 'Overenie podpisu zlyhalo', + stripeRest: 'Zavolať povolenú metódu Stripe API', + stripeRestResult: 'Výsledok Stripe API', + errorPluginStripeRest404: 'Stripe API vrátilo chybu', + mcp: 'Odoslať správu MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol cez Streamable HTTP s odpoveďami v JSON. Anonymné požiadavky fungujú; ponúkané nástroje závisia od oprávnení používateľa. Klienti s verziou protokolu z roku 2025 musia posielať `Accept: application/json, text/event-stream`.', + mcpResult: 'Odpoveď JSON-RPC', + mcpOverrideAccess: 'Preskočiť kontroly prístupu. Len na vývoj.', + mcpGet: 'Nepodporované: server neotvára žiadny stream udalostí', + errorPluginMcpGet405: 'Metóda nie je povolená, použite POST', + mcpProtocolVersion: 'Dohodnutá verzia protokolu MCP, napr. `2025-06-18`.', + mcpResult202: 'Prijaté: telo obsahovalo len notifikácie alebo odpovede', + errorPluginMcp404: 'Neznáma metóda MCP', + errorPluginMcp406: 'Hlavička `Accept` neobsahuje `application/json` alebo `text/event-stream`', + errorPluginMcp413: 'Telo požiadavky je príliš veľké', + errorPluginMcp415: '`Content-Type` musí byť `application/json`', + seoTitle: 'Vygenerovať meta titulok', + seoDescription: 'Vygenerovať meta popis', + seoUrl: 'Vygenerovať URL náhľadu', + seoImage: 'Vygenerovať meta obrázok', + seoBody: + 'Upravovaný dokument: `collectionSlug` alebo `globalSlug`, jeho `id` a aktuálne údaje `doc`. Odovzdáva sa vašej generovacej funkcii.', + seoResult: 'Vygenerovaná hodnota. Prázdny reťazec, ak nie je nastavená generovacia funkcia.', + searchReindex: 'Znovu zostaviť vyhľadávací index pre vybrané kolekcie', + searchReindexResult: 'Súhrn preindexovania', + tenantOptions: 'Zoznam tenantov, ktorých si používateľ môže vybrať', + tenantOptionsResult: 'Možnosti tenantov', + exportDownload: 'Exportovať dokumenty do súboru', + exportDownloadResult: 'Exportovaný súbor', + exportPreview: 'Náhľad exportu', + importPreview: 'Náhľad importného súboru', + previewResult: 'Stránka dokumentov náhľadu', + importFileData: 'Obsah súboru kódovaný v base64.', + r2Upload: 'Nahrať súbor do R2 po častiach', + r2UploadDesc: + 'Tri kroky na jednej ceste. Začiatok: pošlite `collection`, `fileName` a `fileType`. Každá časť: pridajte `multipartId`, `multipartKey`, `multipartNumber` a `signedReceipt` a pošlite bajty. Dokončenie: to isté bez `multipartNumber`, so zoznamom častí v JSON.', + r2UploadResult: 'Nahrávanie spustené, časť nahraná alebo nahrávanie dokončené (kľúč objektu ako text)', + errorStorageR2412: 'Pod týmto kľúčom už súbor existuje', localizationHeading: 'Lokalizácia', localizationNote: diff --git a/src/shared/translations/locales/sl.ts b/src/shared/translations/locales/sl.ts index 823957a..ba1c536 100644 --- a/src/shared/translations/locales/sl.ts +++ b/src/shared/translations/locales/sl.ts @@ -88,12 +88,27 @@ export const sl: PluginDefaultTranslationsObject = { uploadPayloadField: 'Polja {{schema}}, pretvorjena v niz JSON. Primer: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Datoteka', filePartial: 'Del datoteke za zahtevo `Range`', + paramFileVersion: 'Vrne datoteko, shranjeno s tem ID-jem različice.', uploadInstructionsSummary: 'Pridobi navodila za nalaganje datoteke pred shranjevanjem dokumenta', uploadInstructionsResult: 'Kam poslati bajte datoteke in vrednost `file`, ki jo pošljete z zahtevo za ustvarjanje ali posodobitev', uploadStagePutSummary: 'Pošlji bajte datoteke za začasno nalaganje', uploadStageDeleteSummary: 'Izbriši začasno nalaganje', uploadStageResult: 'Končano, brez vsebine', + paramPreferenceKey: 'Ključ nastavitve, npr. `nav`.', + preferenceGet: 'Pridobi nastavitev trenutnega uporabnika', + preferenceGetResult: 'Shranjena nastavitev ali `value: null`, če je ni', + preferenceSet: 'Shrani nastavitev trenutnega uporabnika', + preferenceSetBody: 'Pošljite vrednost v `value`. Telo brez `value` se v celoti shrani kot vrednost.', + preferenceSetResult: 'Shranjena nastavitev', + preferenceDelete: 'Izbriši nastavitev trenutnega uporabnika', + preferenceDeleteResult: 'Izbrisana nastavitev', + reorder: 'Premakni dokumente na razvrstljivem seznamu', + reorderBody: + 'Premakne `docsToMove` za `target` z `newKeyWillBe: "greater"` ali pred njega z `"less"`. `orderableFieldName` je `_order` za razvrstljivo zbirko ali polje vrstnega reda razvrstljivega joina. Brez `target.key` Payload le dodeli ključe vrstnega reda dokumentom, ki jih nimajo.', + reorderResult: + 'Novi ključi vrstnega reda ali `message: "initial migration"`, ko je Payload prvič nastavil ključe vrstnega reda', + errorReorder400: 'Neveljavno telo zahteve; razlog je v `error`', error400: 'Napaka pri preverjanju ali poizvedbi (ValidationError, QueryError)', error401: 'Niste overjeni (AuthenticationError)', @@ -117,6 +132,72 @@ export const sl: PluginDefaultTranslationsObject = { tagJobs: 'Opravila', tagUploads: 'Nalaganja', tagAccess: 'Dostop', + tagReorder: 'Vrstni red', + tagPlugins: 'Vtičniki', + tagPluginsDesc: 'Končne točke, ki jih dodajo uradni vtičniki Payload.', + errorPlugin400: 'Neveljavna zahteva', + errorPlugin401: 'Niste prijavljeni', + errorPlugin403: 'Prepovedano', + errorPlugin404: 'Ni najdeno', + errorPlugin500: 'Notranja napaka strežnika', + ecommerceAddItem: 'Dodaj izdelek v košarico', + ecommerceRemoveItem: 'Odstrani izdelek iz košarice', + ecommerceUpdateItem: 'Spremeni količino izdelka v košarici', + ecommerceClearCart: 'Odstrani vse izdelke iz košarice', + ecommerceMergeCart: 'Združi gostujočo košarico s to košarico', + ecommerceCartAccess: 'Dovoljeno lastniku košarice ali za gostujočo košarico z njenim `secret` v telesu zahteve.', + ecommerceCartResult: 'Posodobljena košarica', + errorPluginEcommerce404: 'Košarica ni najdena ali ni dostopna', + ecommerceQuantity: 'Nova količina ali `{ "$inc": n }` za spremembo za n.', + ecommerceInitiatePayment: 'Začni plačilo z `{{method}}`', + ecommerceConfirmOrder: 'Potrdi plačilo z `{{method}}` in ustvari naročilo', + ecommercePaymentBody: + 'Uporabi `cartID` (s `secret` za gostujočo košarico) ali košarico uporabnika. Brez uporabnika je `customerEmail` obvezen. Plačilni adapter lahko zahteva dodatna polja.', + ecommerceInitiateResult: 'Plačilo se je začelo. Adapter doda svoja polja, npr. client secret.', + ecommerceConfirmResult: 'Naročilo je ustvarjeno', + stripeWebhook: 'Prejmi Stripe webhook dogodke', + stripeWebhookBody: + 'Neobdelan Stripe dogodek, podpisan v glavi `Stripe-Signature`. Kliče ga Stripe, ne odjemalci API-ja.', + stripeWebhookResult: 'Dogodek prejet', + errorPluginStripeWebhook400: 'Preverjanje podpisa ni uspelo', + stripeRest: 'Pokliči dovoljeno metodo Stripe API-ja', + stripeRestResult: 'Rezultat Stripe API-ja', + errorPluginStripeRest404: 'Stripe API je vrnil napako', + mcp: 'Pošlji sporočilo MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol prek Streamable HTTP, z odgovori JSON. Anonimne zahteve delujejo; navedena orodja so odvisna od pravic uporabnika. Odjemalci z različico protokola iz leta 2025 morajo pošiljati `Accept: application/json, text/event-stream`.', + mcpResult: 'Odgovor JSON-RPC', + mcpOverrideAccess: 'Preskoči preverjanja dostopa. Samo za razvoj.', + mcpGet: 'Ni podprto: strežnik ne odpre toka dogodkov', + errorPluginMcpGet405: 'Metoda ni dovoljena, uporabite POST', + mcpProtocolVersion: 'Dogovorjena različica protokola MCP, npr. `2025-06-18`.', + mcpResult202: 'Sprejeto: telo je vsebovalo samo obvestila ali odgovore', + errorPluginMcp404: 'Neznana metoda MCP', + errorPluginMcp406: 'V glavi `Accept` manjka `application/json` ali `text/event-stream`', + errorPluginMcp413: 'Telo zahteve je preveliko', + errorPluginMcp415: '`Content-Type` mora biti `application/json`', + seoTitle: 'Ustvari meta naslov', + seoDescription: 'Ustvari meta opis', + seoUrl: 'Ustvari URL za predogled', + seoImage: 'Ustvari meta sliko', + seoBody: + 'Dokument, ki se ureja: `collectionSlug` ali `globalSlug`, njegov `id` in trenutni podatki `doc`. Posreduje se vaši funkciji za ustvarjanje.', + seoResult: 'Ustvarjena vrednost. Prazen niz, če funkcija za ustvarjanje ni nastavljena.', + searchReindex: 'Ponovno zgradi iskalni indeks za nekatere zbirke', + searchReindexResult: 'Povzetek ponovnega indeksiranja', + tenantOptions: 'Seznam najemnikov, ki jih uporabnik lahko izbere', + tenantOptionsResult: 'Možnosti najemnikov', + exportDownload: 'Izvozi dokumente v datoteko', + exportDownloadResult: 'Izvozna datoteka', + exportPreview: 'Predogled izvoza', + importPreview: 'Predogled uvozne datoteke', + previewResult: 'Stran dokumentov za predogled', + importFileData: 'Vsebina datoteke, kodirana v base64.', + r2Upload: 'Naloži datoteko v R2 po delih', + r2UploadDesc: + 'Trije koraki na eni poti. Začetek: pošljite `collection`, `fileName` in `fileType`. Vsak del: dodajte `multipartId`, `multipartKey`, `multipartNumber` in `signedReceipt` ter pošljite bajte. Zaključek: enako brez `multipartNumber`, s seznamom delov v JSON.', + r2UploadResult: 'Nalaganje se je začelo, del je naložen ali nalaganje je končano (ključ objekta kot besedilo)', + errorStorageR2412: 'Datoteka s tem ključem že obstaja', localizationHeading: 'Lokalizacija', localizationNote: diff --git a/src/shared/translations/locales/sv.ts b/src/shared/translations/locales/sv.ts index e8862b5..89b7522 100644 --- a/src/shared/translations/locales/sv.ts +++ b/src/shared/translations/locales/sv.ts @@ -89,12 +89,27 @@ export const sv: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-strängifierade {{schema}}-fält. Exempel: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Filen', filePartial: 'En del av filen vid en `Range`-begäran', + paramFileVersion: 'Returnerar filen som sparades med detta versions-ID.', uploadInstructionsSummary: 'Hämta instruktioner för att ladda upp en fil innan dokumentet sparas', uploadInstructionsResult: 'Vart filens byte ska skickas och vilket `file`-värde som ska skickas med skapa- eller uppdatera-begäran', uploadStagePutSummary: 'Skicka filens byte för en tillfällig uppladdning', uploadStageDeleteSummary: 'Ta bort en tillfällig uppladdning', uploadStageResult: 'Klart, inget innehåll', + paramPreferenceKey: 'Inställningens nyckel, t.ex. `nav`.', + preferenceGet: 'Hämta en inställning för den aktuella användaren', + preferenceGetResult: 'Den sparade inställningen, eller `value: null` om den saknas', + preferenceSet: 'Spara en inställning för den aktuella användaren', + preferenceSetBody: 'Skicka värdet som `value`. En body utan `value` sparas i sin helhet som värdet.', + preferenceSetResult: 'Den sparade inställningen', + preferenceDelete: 'Ta bort en inställning för den aktuella användaren', + preferenceDeleteResult: 'Den borttagna inställningen', + reorder: 'Flytta dokument i en sorterbar lista', + reorderBody: + 'Flyttar `docsToMove` efter `target` med `newKeyWillBe: "greater"`, eller före med `"less"`. `orderableFieldName` är `_order` för en sorterbar samling, eller ordningsfältet för en sorterbar join. Utan `target.key` ger Payload bara ordningsnycklar till dokument som saknar dem.', + reorderResult: + 'De nya ordningsnycklarna, eller `message: "initial migration"` när Payload satte ordningsnycklarna första gången', + errorReorder400: 'Ogiltig request-body; orsaken står i `error`', error400: 'Validerings- eller frågefel (ValidationError, QueryError)', error401: 'Inte autentiserad (AuthenticationError)', @@ -118,6 +133,72 @@ export const sv: PluginDefaultTranslationsObject = { tagJobs: 'Jobb', tagUploads: 'Uppladdningar', tagAccess: 'Åtkomst', + tagReorder: 'Ordning', + tagPlugins: 'Plugins', + tagPluginsDesc: 'Slutpunkter som läggs till av officiella Payload-plugins.', + errorPlugin400: 'Ogiltig begäran', + errorPlugin401: 'Inte autentiserad', + errorPlugin403: 'Förbjudet', + errorPlugin404: 'Hittades inte', + errorPlugin500: 'Internt serverfel', + ecommerceAddItem: 'Lägg en vara i varukorgen', + ecommerceRemoveItem: 'Ta bort en vara från varukorgen', + ecommerceUpdateItem: 'Ändra antalet för en vara i varukorgen', + ecommerceClearCart: 'Ta bort alla varor från varukorgen', + ecommerceMergeCart: 'Slå ihop en gästvarukorg med den här varukorgen', + ecommerceCartAccess: 'Tillåtet för varukorgens ägare, eller för en gästvarukorg med dess `secret` i body.', + ecommerceCartResult: 'Den uppdaterade varukorgen', + errorPluginEcommerce404: 'Varukorgen hittades inte eller är inte åtkomlig', + ecommerceQuantity: 'Nytt antal, eller `{ "$inc": n }` för att ändra det med n.', + ecommerceInitiatePayment: 'Starta en betalning med `{{method}}`', + ecommerceConfirmOrder: 'Bekräfta betalningen med `{{method}}` och skapa ordern', + ecommercePaymentBody: + 'Använder `cartID` (med `secret` för en gästvarukorg) eller användarens varukorg. Utan användare krävs `customerEmail`. Betalningsadaptern kan behöva fler fält.', + ecommerceInitiateResult: 'Betalningen har startats. Adaptern lägger till egna fält, t.ex. en client secret.', + ecommerceConfirmResult: 'Ordern skapades', + stripeWebhook: 'Ta emot Stripe-webhookhändelser', + stripeWebhookBody: + 'Den råa Stripe-händelsen, signerad i headern `Stripe-Signature`. Anropas av Stripe, inte av API-klienter.', + stripeWebhookResult: 'Händelsen mottogs', + errorPluginStripeWebhook400: 'Signaturkontrollen misslyckades', + stripeRest: 'Anropa en tillåten Stripe API-metod', + stripeRestResult: 'Resultatet från Stripe API', + errorPluginStripeRest404: 'Stripe API returnerade ett fel', + mcp: 'Skicka ett MCP JSON-RPC-meddelande', + mcpDesc: + 'Model Context Protocol över Streamable HTTP med JSON-svar. Anonyma begäranden fungerar; vilka verktyg som listas beror på användarens behörighet. Klienter med en protokollversion från 2025 måste skicka `Accept: application/json, text/event-stream`.', + mcpResult: 'JSON-RPC-svar', + mcpOverrideAccess: 'Hoppa över behörighetskontroller. Endast för utveckling.', + mcpGet: 'Stöds inte: servern öppnar ingen händelseström', + errorPluginMcpGet405: 'Metoden är inte tillåten, använd POST', + mcpProtocolVersion: 'Förhandlad MCP-protokollversion, t.ex. `2025-06-18`.', + mcpResult202: 'Accepterad: body innehöll bara notifieringar eller svar', + errorPluginMcp404: 'Okänd MCP-metod', + errorPluginMcp406: 'Headern `Accept` saknar `application/json` eller `text/event-stream`', + errorPluginMcp413: 'Begärans body är för stor', + errorPluginMcp415: '`Content-Type` måste vara `application/json`', + seoTitle: 'Generera metatiteln', + seoDescription: 'Generera metabeskrivningen', + seoUrl: 'Generera förhandsgransknings-URL:en', + seoImage: 'Generera metabilden', + seoBody: + 'Dokumentet som redigeras: `collectionSlug` eller `globalSlug`, dess `id` och aktuella `doc`-data. Skickas vidare till din generate-funktion.', + seoResult: 'Det genererade värdet. En tom sträng när ingen generate-funktion är angiven.', + searchReindex: 'Bygg om sökindexet för vissa samlingar', + searchReindexResult: 'Sammanfattning av omindexering', + tenantOptions: 'Lista de tenants som användaren kan välja', + tenantOptionsResult: 'Tenant-alternativ', + exportDownload: 'Exportera dokument till en fil', + exportDownloadResult: 'Exportfilen', + exportPreview: 'Förhandsgranska en export', + importPreview: 'Förhandsgranska en importfil', + previewResult: 'En sida med förhandsgranskningsdokument', + importFileData: 'Filinnehåll, base64-kodat.', + r2Upload: 'Ladda upp en fil till R2 i delar', + r2UploadDesc: + 'Tre steg på en route. Start: skicka `collection`, `fileName` och `fileType`. Varje del: lägg till `multipartId`, `multipartKey`, `multipartNumber` och `signedReceipt` och skicka byten. Slutför: samma sak utan `multipartNumber`, med JSON-listan över delar.', + r2UploadResult: 'Uppladdningen startad, del uppladdad eller uppladdningen klar (objektnyckeln som text)', + errorStorageR2412: 'Det finns redan en fil med den här nyckeln', localizationHeading: 'Lokalisering', localizationNote: diff --git a/src/shared/translations/locales/ta.ts b/src/shared/translations/locales/ta.ts index 014a2d3..f2bd43d 100644 --- a/src/shared/translations/locales/ta.ts +++ b/src/shared/translations/locales/ta.ts @@ -88,12 +88,26 @@ export const ta: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON-stringified {{schema}} புலங்கள். எடுத்துக்காட்டு: `{"alt":"A caption"}`.', fileServe: 'கோப்பு', filePartial: '`Range` கோரிக்கைக்கான கோப்பின் ஒரு பகுதி', + paramFileVersion: 'இந்தப் பதிப்பு ID உடன் சேமிக்கப்பட்ட கோப்பைத் தருகிறது.', uploadInstructionsSummary: 'ஆவணத்தைச் சேமிக்கும் முன் கோப்பைப் பதிவேற்றுவதற்கான வழிமுறைகளைப் பெறவும்', uploadInstructionsResult: 'கோப்பின் பைட்டுகளை எங்கு அனுப்ப வேண்டும் மற்றும் உருவாக்கு அல்லது புதுப்பி கோரிக்கையுடன் அனுப்ப வேண்டிய `file` மதிப்பு', uploadStagePutSummary: 'தற்காலிகப் பதிவேற்றத்திற்குக் கோப்பின் பைட்டுகளை அனுப்பவும்', uploadStageDeleteSummary: 'தற்காலிகப் பதிவேற்றத்தை நீக்கவும்', uploadStageResult: 'முடிந்தது, உள்ளடக்கம் இல்லை', + paramPreferenceKey: 'விருப்பத்தின் கீ, எ.கா. `nav`.', + preferenceGet: 'தற்போதைய பயனரின் ஒரு விருப்பத்தைப் பெறவும்', + preferenceGetResult: 'சேமிக்கப்பட்ட விருப்பம், அல்லது இல்லையெனில் `value: null`', + preferenceSet: 'தற்போதைய பயனரின் ஒரு விருப்பத்தைச் சேமிக்கவும்', + preferenceSetBody: 'மதிப்பை `value`-இல் அனுப்பவும். `value` இல்லாத body முழுவதும் மதிப்பாகச் சேமிக்கப்படும்.', + preferenceSetResult: 'சேமிக்கப்பட்ட விருப்பம்', + preferenceDelete: 'தற்போதைய பயனரின் ஒரு விருப்பத்தை நீக்கவும்', + preferenceDeleteResult: 'நீக்கப்பட்ட விருப்பம்', + reorder: 'வரிசைப்படுத்தக்கூடிய பட்டியலில் ஆவணங்களை நகர்த்தவும்', + reorderBody: + '`newKeyWillBe: "greater"` எனில் `docsToMove`-ஐ `target`-க்குப் பின்னும், `"less"` எனில் முன்னும் நகர்த்துகிறது. வரிசைப்படுத்தக்கூடிய தொகுப்புக்கு `orderableFieldName` என்பது `_order`, வரிசைப்படுத்தக்கூடிய join-க்கு அதன் வரிசைப் புலம். `target.key` இல்லையெனில், வரிசைக் கீ இல்லாத ஆவணங்களுக்கு மட்டும் Payload வரிசைக் கீகளை வழங்குகிறது.', + reorderResult: 'புதிய வரிசைக் கீகள், அல்லது Payload முதல் முறையாக வரிசைக் கீகளை அமைத்தபோது `message: "initial migration"`', + errorReorder400: 'தவறான கோரிக்கை body; காரணம் `error`-இல் உள்ளது', error400: 'சரிபார்ப்பு அல்லது வினவல் பிழை (ValidationError, QueryError)', error401: 'அங்கீகரிக்கப்படவில்லை (AuthenticationError)', @@ -117,6 +131,71 @@ export const ta: PluginDefaultTranslationsObject = { tagJobs: 'பணிகள்', tagUploads: 'பதிவேற்றங்கள்', tagAccess: 'அணுகல்', + tagReorder: 'மறுவரிசை', + tagPlugins: 'செருகுநிரல்கள்', + tagPluginsDesc: 'அதிகாரப்பூர்வ Payload செருகுநிரல்கள் சேர்க்கும் எண்ட்பாயிண்ட்கள்.', + errorPlugin400: 'தவறான கோரிக்கை', + errorPlugin401: 'அங்கீகரிக்கப்படவில்லை', + errorPlugin403: 'தடைசெய்யப்பட்டது', + errorPlugin404: 'கிடைக்கவில்லை', + errorPlugin500: 'சேவையக உள் பிழை', + ecommerceAddItem: 'வண்டியில் ஒரு பொருளைச் சேர்', + ecommerceRemoveItem: 'வண்டியிலிருந்து ஒரு பொருளை நீக்கு', + ecommerceUpdateItem: 'வண்டிப் பொருளின் அளவை மாற்று', + ecommerceClearCart: 'வண்டியிலுள்ள எல்லாப் பொருட்களையும் நீக்கு', + ecommerceMergeCart: 'விருந்தினர் வண்டியை இந்த வண்டியுடன் இணை', + ecommerceCartAccess: 'வண்டி உரிமையாளருக்கு அல்லது body-யில் `secret` உள்ள விருந்தினர் வண்டிக்கு அனுமதி.', + ecommerceCartResult: 'புதுப்பிக்கப்பட்ட வண்டி', + errorPluginEcommerce404: 'வண்டி கிடைக்கவில்லை அல்லது அணுக முடியாது', + ecommerceQuantity: 'புதிய அளவு, அல்லது n அளவில் மாற்ற `{ "$inc": n }`.', + ecommerceInitiatePayment: '`{{method}}` மூலம் கட்டணத்தைத் தொடங்கு', + ecommerceConfirmOrder: '`{{method}}` மூலம் கட்டணத்தை உறுதிசெய்து ஆர்டரை உருவாக்கு', + ecommercePaymentBody: + '`cartID` (விருந்தினர் வண்டிக்கு `secret` உடன்) அல்லது பயனரின் வண்டியைப் பயன்படுத்துகிறது. பயனர் இல்லையெனில் `customerEmail` கட்டாயம். கட்டண அடாப்டருக்குக் கூடுதல் புலங்கள் தேவைப்படலாம்.', + ecommerceInitiateResult: 'கட்டணம் தொடங்கியது. அடாப்டர் தன் புலங்களைச் சேர்க்கும், எ.கா. client secret.', + ecommerceConfirmResult: 'ஆர்டர் உருவாக்கப்பட்டது', + stripeWebhook: 'Stripe webhook நிகழ்வுகளைப் பெறு', + stripeWebhookBody: '`Stripe-Signature` தலைப்பில் கையொப்பமிட்ட மூல Stripe நிகழ்வு. API கிளையன்ட்கள் அல்ல, Stripe அழைக்கிறது.', + stripeWebhookResult: 'நிகழ்வு பெறப்பட்டது', + errorPluginStripeWebhook400: 'கையொப்பச் சரிபார்ப்பு தோல்வியடைந்தது', + stripeRest: 'அனுமதிக்கப்பட்ட Stripe API முறையை அழை', + stripeRestResult: 'Stripe API முடிவு', + errorPluginStripeRest404: 'Stripe API பிழையைத் தந்தது', + mcp: 'MCP JSON-RPC செய்தியை அனுப்பு', + mcpDesc: + 'Streamable HTTP வழியாக Model Context Protocol, JSON பதில்களுடன். அநாமதேயக் கோரிக்கைகள் வேலை செய்யும்; காட்டப்படும் கருவிகள் பயனரின் அணுகலைப் பொறுத்தது. 2025 நெறிமுறைப் பதிப்பைப் பயன்படுத்தும் கிளையன்ட்கள் `Accept: application/json, text/event-stream` அனுப்ப வேண்டும்.', + mcpResult: 'JSON-RPC பதில்', + mcpOverrideAccess: 'அணுகல் சோதனைகளைத் தவிர். மேம்பாட்டுக்கு மட்டும்.', + mcpGet: 'ஆதரிக்கப்படவில்லை: சேவையகம் நிகழ்வு ஓட்டத்தைத் திறப்பதில்லை', + errorPluginMcpGet405: 'இந்த முறை அனுமதிக்கப்படவில்லை, POST பயன்படுத்தவும்', + mcpProtocolVersion: 'ஒப்புக்கொள்ளப்பட்ட MCP நெறிமுறைப் பதிப்பு, எ.கா. `2025-06-18`.', + mcpResult202: 'ஏற்கப்பட்டது: body-யில் அறிவிப்புகள் அல்லது பதில்கள் மட்டுமே இருந்தன', + errorPluginMcp404: 'தெரியாத MCP முறை', + errorPluginMcp406: '`Accept` தலைப்பில் `application/json` அல்லது `text/event-stream` இல்லை', + errorPluginMcp413: 'கோரிக்கை body மிகப் பெரியது', + errorPluginMcp415: '`Content-Type` `application/json` ஆக இருக்க வேண்டும்', + seoTitle: 'மெட்டா தலைப்பை உருவாக்கு', + seoDescription: 'மெட்டா விளக்கத்தை உருவாக்கு', + seoUrl: 'முன்னோட்ட URL-ஐ உருவாக்கு', + seoImage: 'மெட்டா படத்தை உருவாக்கு', + seoBody: + 'திருத்தப்படும் ஆவணம்: `collectionSlug` அல்லது `globalSlug`, அதன் `id`, தற்போதைய `doc` தரவு. உங்கள் உருவாக்கச் செயல்பாட்டுக்கு அனுப்பப்படும்.', + seoResult: 'உருவாக்கப்பட்ட மதிப்பு. உருவாக்கச் செயல்பாடு அமைக்கப்படாவிட்டால் வெற்றுச் சரம்.', + searchReindex: 'சில தொகுப்புகளுக்குத் தேடல் குறியீட்டை மீண்டும் உருவாக்கு', + searchReindexResult: 'மீள்குறியீட்டுச் சுருக்கம்', + tenantOptions: 'பயனர் தேர்வுசெய்யக்கூடிய குத்தகைதாரர்களைப் பட்டியலிடு', + tenantOptionsResult: 'குத்தகைதாரர் விருப்பங்கள்', + exportDownload: 'ஆவணங்களைக் கோப்பாக ஏற்றுமதி செய்', + exportDownloadResult: 'ஏற்றுமதிக் கோப்பு', + exportPreview: 'ஏற்றுமதியை முன்னோட்டமிடு', + importPreview: 'இறக்குமதிக் கோப்பை முன்னோட்டமிடு', + previewResult: 'முன்னோட்ட ஆவணங்களின் ஒரு பக்கம்', + importFileData: 'கோப்பு உள்ளடக்கம், base64 குறியாக்கத்தில்.', + r2Upload: 'கோப்பைப் பகுதிகளாக R2-க்குப் பதிவேற்று', + r2UploadDesc: + 'ஒரே வழியில் மூன்று படிகள். தொடக்கம்: `collection`, `fileName`, `fileType` அனுப்பவும். ஒவ்வொரு பகுதியும்: `multipartId`, `multipartKey`, `multipartNumber`, `signedReceipt` சேர்த்து பைட்டுகளை அனுப்பவும். நிறைவு: `multipartNumber` இல்லாமல் அதே, பகுதிகளின் JSON பட்டியலுடன்.', + r2UploadResult: 'பதிவேற்றம் தொடங்கியது, பகுதி பதிவேற்றப்பட்டது அல்லது பதிவேற்றம் முடிந்தது (பொருள் விசை உரையாக)', + errorStorageR2412: 'இந்த விசையில் ஏற்கனவே ஒரு கோப்பு உள்ளது', localizationHeading: 'உள்ளூராக்கம்', localizationNote: diff --git a/src/shared/translations/locales/th.ts b/src/shared/translations/locales/th.ts index 5ae41de..9a156f4 100644 --- a/src/shared/translations/locales/th.ts +++ b/src/shared/translations/locales/th.ts @@ -84,11 +84,25 @@ export const th: PluginDefaultTranslationsObject = { uploadPayloadField: 'ฟิลด์ {{schema}} ในรูปแบบ JSON string ตัวอย่าง: `{"alt":"A caption"}`', fileServe: 'ไฟล์', filePartial: 'ส่วนหนึ่งของไฟล์ สำหรับคำขอ `Range`', + paramFileVersion: 'ส่งคืนไฟล์ที่บันทึกไว้กับ ID เวอร์ชันนี้', uploadInstructionsSummary: 'รับคำแนะนำในการอัปโหลดไฟล์ก่อนบันทึกเอกสาร', uploadInstructionsResult: 'ปลายทางสำหรับส่งไบต์ของไฟล์ และค่า `file` ที่ต้องส่งไปกับคำขอสร้างหรืออัปเดต', uploadStagePutSummary: 'ส่งไบต์ของไฟล์สำหรับการอัปโหลดชั่วคราว', uploadStageDeleteSummary: 'ลบการอัปโหลดชั่วคราว', uploadStageResult: 'เสร็จแล้ว ไม่มีเนื้อหา', + paramPreferenceKey: 'คีย์ของการตั้งค่า เช่น `nav`', + preferenceGet: 'รับการตั้งค่าของผู้ใช้ปัจจุบัน', + preferenceGetResult: 'การตั้งค่าที่บันทึกไว้ หรือ `value: null` หากไม่มี', + preferenceSet: 'บันทึกการตั้งค่าของผู้ใช้ปัจจุบัน', + preferenceSetBody: 'ส่งค่าใน `value` หาก body ไม่มี `value` จะบันทึกทั้ง body เป็นค่า', + preferenceSetResult: 'การตั้งค่าที่บันทึกแล้ว', + preferenceDelete: 'ลบการตั้งค่าของผู้ใช้ปัจจุบัน', + preferenceDeleteResult: 'การตั้งค่าที่ถูกลบ', + reorder: 'ย้ายเอกสารในรายการที่จัดลำดับได้', + reorderBody: + 'ย้าย `docsToMove` ไปไว้หลัง `target` เมื่อ `newKeyWillBe: "greater"` หรือไว้ก่อนเมื่อ `"less"` ส่วน `orderableFieldName` คือ `_order` สำหรับคอลเลกชันที่จัดลำดับได้ หรือฟิลด์ลำดับของ join ที่จัดลำดับได้ หากไม่มี `target.key` Payload จะกำหนดคีย์ลำดับให้เฉพาะเอกสารที่ยังไม่มีเท่านั้น', + reorderResult: 'คีย์ลำดับใหม่ หรือ `message: "initial migration"` เมื่อ Payload กำหนดคีย์ลำดับเป็นครั้งแรก', + errorReorder400: 'body ของคำขอไม่ถูกต้อง ดูเหตุผลได้ใน `error`', error400: 'ข้อผิดพลาดในการตรวจสอบความถูกต้องหรือการค้นหา (ValidationError, QueryError)', error401: 'ยังไม่ได้ยืนยันตัวตน (AuthenticationError)', @@ -111,6 +125,71 @@ export const th: PluginDefaultTranslationsObject = { tagJobs: 'งาน', tagUploads: 'การอัปโหลด', tagAccess: 'การเข้าถึง', + tagReorder: 'จัดลำดับใหม่', + tagPlugins: 'ปลั๊กอิน', + tagPluginsDesc: 'เอนด์พอยต์ที่ปลั๊กอินทางการของ Payload เพิ่มเข้ามา', + errorPlugin400: 'คำขอไม่ถูกต้อง', + errorPlugin401: 'ยังไม่ได้ยืนยันตัวตน', + errorPlugin403: 'ไม่มีสิทธิ์', + errorPlugin404: 'ไม่พบ', + errorPlugin500: 'ข้อผิดพลาดภายในเซิร์ฟเวอร์', + ecommerceAddItem: 'เพิ่มสินค้าลงตะกร้า', + ecommerceRemoveItem: 'นำสินค้าออกจากตะกร้า', + ecommerceUpdateItem: 'เปลี่ยนจำนวนสินค้าในตะกร้า', + ecommerceClearCart: 'นำสินค้าทั้งหมดออกจากตะกร้า', + ecommerceMergeCart: 'รวมตะกร้าของผู้เยี่ยมชมเข้ากับตะกร้านี้', + ecommerceCartAccess: 'อนุญาตสำหรับเจ้าของตะกร้า หรือตะกร้าของผู้เยี่ยมชมที่มี `secret` ใน body', + ecommerceCartResult: 'ตะกร้าที่อัปเดตแล้ว', + errorPluginEcommerce404: 'ไม่พบตะกร้าหรือเข้าถึงไม่ได้', + ecommerceQuantity: 'จำนวนใหม่ หรือ `{ "$inc": n }` เพื่อเปลี่ยนทีละ n', + ecommerceInitiatePayment: 'เริ่มการชำระเงินด้วย `{{method}}`', + ecommerceConfirmOrder: 'ยืนยันการชำระเงินด้วย `{{method}}` และสร้างคำสั่งซื้อ', + ecommercePaymentBody: + 'ใช้ `cartID` (พร้อม `secret` สำหรับตะกร้าของผู้เยี่ยมชม) หรือตะกร้าของผู้ใช้ หากไม่มีผู้ใช้ ต้องระบุ `customerEmail` อะแดปเตอร์การชำระเงินอาจต้องการฟิลด์เพิ่มเติม', + ecommerceInitiateResult: 'เริ่มการชำระเงินแล้ว อะแดปเตอร์จะเพิ่มฟิลด์ของตัวเอง เช่น client secret', + ecommerceConfirmResult: 'สร้างคำสั่งซื้อแล้ว', + stripeWebhook: 'รับอีเวนต์ webhook จาก Stripe', + stripeWebhookBody: 'อีเวนต์ Stripe ดิบที่ลงนามในเฮดเดอร์ `Stripe-Signature` Stripe เป็นผู้เรียก ไม่ใช่ไคลเอนต์ API', + stripeWebhookResult: 'ได้รับอีเวนต์แล้ว', + errorPluginStripeWebhook400: 'การตรวจสอบลายเซ็นล้มเหลว', + stripeRest: 'เรียกเมธอด Stripe API ที่อนุญาต', + stripeRestResult: 'ผลลัพธ์จาก Stripe API', + errorPluginStripeRest404: 'Stripe API ส่งข้อผิดพลาดกลับมา', + mcp: 'ส่งข้อความ MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol ผ่าน Streamable HTTP ตอบกลับเป็น JSON ใช้คำขอแบบไม่ระบุตัวตนได้ เครื่องมือที่แสดงขึ้นอยู่กับสิทธิ์ของผู้ใช้ ไคลเอนต์ที่ใช้โปรโตคอลเวอร์ชันปี 2025 ต้องส่ง `Accept: application/json, text/event-stream`', + mcpResult: 'การตอบกลับ JSON-RPC', + mcpOverrideAccess: 'ข้ามการตรวจสอบสิทธิ์ ใช้สำหรับการพัฒนาเท่านั้น', + mcpGet: 'ไม่รองรับ: เซิร์ฟเวอร์ไม่เปิดสตรีมอีเวนต์', + errorPluginMcpGet405: 'ไม่อนุญาตเมธอดนี้ ให้ใช้ POST', + mcpProtocolVersion: 'เวอร์ชันโปรโตคอล MCP ที่ตกลงกันแล้ว เช่น `2025-06-18`', + mcpResult202: 'ยอมรับแล้ว: body มีเพียงการแจ้งเตือนหรือการตอบกลับ', + errorPluginMcp404: 'ไม่รู้จักเมธอด MCP', + errorPluginMcp406: 'เฮดเดอร์ `Accept` ไม่มี `application/json` หรือ `text/event-stream`', + errorPluginMcp413: 'body ของคำขอมีขนาดใหญ่เกินไป', + errorPluginMcp415: '`Content-Type` ต้องเป็น `application/json`', + seoTitle: 'สร้างชื่อเมตา', + seoDescription: 'สร้างคำอธิบายเมตา', + seoUrl: 'สร้าง URL พรีวิว', + seoImage: 'สร้างรูปภาพเมตา', + seoBody: + 'เอกสารที่กำลังแก้ไข: `collectionSlug` หรือ `globalSlug`, `id` ของเอกสาร และข้อมูล `doc` ปัจจุบัน ส่งต่อให้ฟังก์ชันสร้างของคุณ', + seoResult: 'ค่าที่สร้างขึ้น เป็นสตริงว่างเมื่อไม่ได้ตั้งฟังก์ชันสร้าง', + searchReindex: 'สร้างดัชนีการค้นหาใหม่สำหรับบางคอลเลกชัน', + searchReindexResult: 'สรุปการสร้างดัชนีใหม่', + tenantOptions: 'แสดงรายการผู้เช่าที่ผู้ใช้เลือกได้', + tenantOptionsResult: 'ตัวเลือกผู้เช่า', + exportDownload: 'ส่งออกเอกสารเป็นไฟล์', + exportDownloadResult: 'ไฟล์ที่ส่งออก', + exportPreview: 'พรีวิวการส่งออก', + importPreview: 'พรีวิวไฟล์นำเข้า', + previewResult: 'เอกสารพรีวิวหนึ่งหน้า', + importFileData: 'เนื้อหาไฟล์ เข้ารหัสแบบ base64', + r2Upload: 'อัปโหลดไฟล์ไปยัง R2 เป็นส่วน ๆ', + r2UploadDesc: + 'สามขั้นตอนบนเส้นทางเดียว เริ่ม: ส่ง `collection`, `fileName` และ `fileType` แต่ละส่วน: เพิ่ม `multipartId`, `multipartKey`, `multipartNumber` และ `signedReceipt` แล้วส่งไบต์ เสร็จสิ้น: ส่งแบบเดิมโดยไม่มี `multipartNumber` พร้อมรายการส่วนแบบ JSON', + r2UploadResult: 'เริ่มอัปโหลด อัปโหลดส่วนแล้ว หรืออัปโหลดเสร็จ (คีย์ของออบเจ็กต์เป็นข้อความ)', + errorStorageR2412: 'มีไฟล์ที่คีย์นี้อยู่แล้ว', localizationHeading: 'การแปลภาษา', localizationNote: diff --git a/src/shared/translations/locales/tr.ts b/src/shared/translations/locales/tr.ts index 4b7d2d1..d09d98d 100644 --- a/src/shared/translations/locales/tr.ts +++ b/src/shared/translations/locales/tr.ts @@ -88,12 +88,28 @@ export const tr: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON olarak dizgeleştirilmiş {{schema}} alanları. Örnek: `{\\"alt\\":\\"A caption\\"}`.', fileServe: 'Dosya', filePartial: '`Range` isteği için dosyanın bir bölümü', + paramFileVersion: 'Bu sürüm kimliğiyle kaydedilen dosyayı döndürür.', uploadInstructionsSummary: 'Dokümanı kaydetmeden önce dosya yükleme talimatlarını alın', uploadInstructionsResult: 'Dosya baytlarının nereye gönderileceği ve oluşturma ya da güncelleme isteğiyle gönderilecek `file` değeri', uploadStagePutSummary: 'Geçici yükleme için dosya baytlarını gönderin', uploadStageDeleteSummary: 'Geçici yüklemeyi silin', uploadStageResult: 'Tamamlandı, içerik yok', + paramPreferenceKey: 'Tercih anahtarı, örn. `nav`.', + preferenceGet: 'Geçerli kullanıcının bir tercihini getir', + preferenceGetResult: 'Kaydedilmiş tercih veya yoksa `value: null`', + preferenceSet: 'Geçerli kullanıcının bir tercihini kaydet', + preferenceSetBody: + 'Değeri `value` içinde gönderin. `value` içermeyen bir gövde, olduğu gibi değer olarak kaydedilir.', + preferenceSetResult: 'Kaydedilen tercih', + preferenceDelete: 'Geçerli kullanıcının bir tercihini sil', + preferenceDeleteResult: 'Silinen tercih', + reorder: 'Sıralanabilir bir listede dokümanları taşı', + reorderBody: + '`docsToMove` öğelerini `newKeyWillBe: "greater"` ile `target` sonrasına, `"less"` ile öncesine taşır. `orderableFieldName`, sıralanabilir bir koleksiyon için `_order`, sıralanabilir bir join için ise onun sıra alanıdır. `target.key` olmadan Payload yalnızca sıra anahtarı olmayan dokümanlara sıra anahtarı atar.', + reorderResult: + 'Yeni sıra anahtarları veya Payload sıra anahtarlarını ilk kez atadığında `message: "initial migration"`', + errorReorder400: 'Geçersiz istek gövdesi; neden `error` içinde', error400: 'Doğrulama veya sorgu hatası (ValidationError, QueryError)', error401: 'Kimlik doğrulanmadı (AuthenticationError)', @@ -117,6 +133,72 @@ export const tr: PluginDefaultTranslationsObject = { tagJobs: 'İşler', tagUploads: 'Yüklemeler', tagAccess: 'Erişim', + tagReorder: 'Sıralama', + tagPlugins: 'Eklentiler', + tagPluginsDesc: 'Resmi Payload eklentilerinin eklediği uç noktalar.', + errorPlugin400: 'Geçersiz istek', + errorPlugin401: 'Kimlik doğrulanmadı', + errorPlugin403: 'Yasak', + errorPlugin404: 'Bulunamadı', + errorPlugin500: 'Dahili sunucu hatası', + ecommerceAddItem: 'Sepete ürün ekle', + ecommerceRemoveItem: 'Sepetten ürün çıkar', + ecommerceUpdateItem: 'Sepetteki bir ürünün miktarını değiştir', + ecommerceClearCart: 'Sepetteki tüm ürünleri çıkar', + ecommerceMergeCart: 'Bir misafir sepetini bu sepetle birleştir', + ecommerceCartAccess: 'Sepet sahibine veya gövdede `secret` değeri olan misafir sepetine izin verilir.', + ecommerceCartResult: 'Güncellenen sepet', + errorPluginEcommerce404: 'Sepet bulunamadı veya erişilemiyor', + ecommerceQuantity: 'Yeni miktar ya da n kadar değiştirmek için `{ "$inc": n }`.', + ecommerceInitiatePayment: '`{{method}}` ile ödeme başlat', + ecommerceConfirmOrder: '`{{method}}` ile ödemeyi onayla ve siparişi oluştur', + ecommercePaymentBody: + '`cartID` (misafir sepeti için `secret` ile) veya kullanıcının sepetini kullanır. Kullanıcı yoksa `customerEmail` zorunludur. Ödeme adaptörü başka alanlar isteyebilir.', + ecommerceInitiateResult: 'Ödeme başlatıldı. Adaptör kendi alanlarını ekler, ör. bir client secret.', + ecommerceConfirmResult: 'Sipariş oluşturuldu', + stripeWebhook: 'Stripe webhook olaylarını al', + stripeWebhookBody: + '`Stripe-Signature` başlığında imzalanmış ham Stripe olayı. API istemcileri değil, Stripe çağırır.', + stripeWebhookResult: 'Olay alındı', + errorPluginStripeWebhook400: 'İmza doğrulaması başarısız oldu', + stripeRest: 'İzin verilen bir Stripe API yöntemini çağır', + stripeRestResult: 'Stripe API sonucu', + errorPluginStripeRest404: 'Stripe API bir hata döndürdü', + mcp: 'MCP JSON-RPC mesajı gönder', + mcpDesc: + 'Streamable HTTP üzerinden, JSON yanıtlı Model Context Protocol. Anonim istekler çalışır; listelenen araçlar kullanıcının yetkilerine bağlıdır. 2025 protokol sürümünü kullanan istemciler `Accept: application/json, text/event-stream` göndermelidir.', + mcpResult: 'JSON-RPC yanıtı', + mcpOverrideAccess: 'Erişim kontrollerini atla. Yalnızca geliştirme için.', + mcpGet: 'Desteklenmiyor: sunucu bir olay akışı açmaz', + errorPluginMcpGet405: 'Yönteme izin verilmiyor, POST kullanın', + mcpProtocolVersion: 'Üzerinde anlaşılan MCP protokol sürümü, ör. `2025-06-18`.', + mcpResult202: 'Kabul edildi: gövdede yalnızca bildirimler veya yanıtlar vardı', + errorPluginMcp404: 'Bilinmeyen MCP yöntemi', + errorPluginMcp406: '`Accept` başlığında `application/json` veya `text/event-stream` yok', + errorPluginMcp413: 'İstek gövdesi çok büyük', + errorPluginMcp415: '`Content-Type` `application/json` olmalıdır', + seoTitle: 'Meta başlığı oluştur', + seoDescription: 'Meta açıklamayı oluştur', + seoUrl: 'Önizleme URL’sini oluştur', + seoImage: 'Meta görseli oluştur', + seoBody: + 'Düzenlenen doküman: `collectionSlug` veya `globalSlug`, `id` değeri ve güncel `doc` verisi. Generate fonksiyonunuza iletilir.', + seoResult: 'Oluşturulan değer. Generate fonksiyonu ayarlı değilse boş bir dize.', + searchReindex: 'Bazı koleksiyonlar için arama dizinini yeniden oluştur', + searchReindexResult: 'Yeniden dizinleme özeti', + tenantOptions: 'Kullanıcının seçebileceği kiracıları listele', + tenantOptionsResult: 'Kiracı seçenekleri', + exportDownload: 'Dokümanları bir dosyaya dışa aktar', + exportDownloadResult: 'Dışa aktarma dosyası', + exportPreview: 'Dışa aktarmayı önizle', + importPreview: 'İçe aktarma dosyasını önizle', + previewResult: 'Bir sayfa önizleme dokümanı', + importFileData: 'Dosya içeriği, base64 ile kodlanmış.', + r2Upload: 'R2’ye parçalar halinde dosya yükle', + r2UploadDesc: + 'Tek rotada üç adım. Başlangıç: `collection`, `fileName` ve `fileType` gönderin. Her parça: `multipartId`, `multipartKey`, `multipartNumber` ve `signedReceipt` ekleyip baytları gönderin. Bitiş: aynısını `multipartNumber` olmadan, parçaların JSON listesiyle gönderin.', + r2UploadResult: 'Yükleme başladı, parça yüklendi veya yükleme tamamlandı (metin olarak nesne anahtarı)', + errorStorageR2412: 'Bu anahtarda zaten bir dosya var', localizationHeading: 'Yerelleştirme', localizationNote: diff --git a/src/shared/translations/locales/uk.ts b/src/shared/translations/locales/uk.ts index 68753c9..6d4f134 100644 --- a/src/shared/translations/locales/uk.ts +++ b/src/shared/translations/locales/uk.ts @@ -86,12 +86,26 @@ export const uk: PluginDefaultTranslationsObject = { uploadPayloadField: 'Поля {{schema}} у вигляді JSON-рядка. Приклад: `{"alt":"A caption"}`.', fileServe: 'Файл', filePartial: 'Частина файлу для запиту з `Range`', + paramFileVersion: 'Віддає файл, збережений з цим ID версії.', uploadInstructionsSummary: 'Отримати інструкції для завантаження файлу перед збереженням документа', uploadInstructionsResult: 'Куди надіслати байти файлу і яке значення `file` передати в запиті на створення чи оновлення', uploadStagePutSummary: 'Надіслати байти файлу для тимчасового завантаження', uploadStageDeleteSummary: 'Видалити тимчасове завантаження', uploadStageResult: 'Готово, без вмісту', + paramPreferenceKey: 'Ключ налаштування, наприклад `nav`.', + preferenceGet: 'Отримати налаштування поточного користувача', + preferenceGetResult: 'Збережене налаштування або `value: null`, якщо його немає', + preferenceSet: 'Зберегти налаштування поточного користувача', + preferenceSetBody: 'Передайте значення в `value`. Тіло без `value` зберігається цілком як значення.', + preferenceSetResult: 'Збережене налаштування', + preferenceDelete: 'Видалити налаштування поточного користувача', + preferenceDeleteResult: 'Видалене налаштування', + reorder: 'Перемістити документи у впорядковуваному списку', + reorderBody: + 'Переміщує `docsToMove` після `target` за `newKeyWillBe: "greater"` або перед ним за `"less"`. `orderableFieldName` дорівнює `_order` для впорядковуваної колекції або полю порядку впорядковуваного join. Без `target.key` Payload лише призначає ключі порядку документам, які їх не мають.', + reorderResult: 'Нові ключі порядку або `message: "initial migration"`, коли Payload уперше призначив ключі порядку', + errorReorder400: 'Некоректне тіло запиту; причину вказано в `error`', error400: 'Помилка валідації або запиту (ValidationError, QueryError)', error401: 'Не автентифіковано (AuthenticationError)', @@ -115,6 +129,72 @@ export const uk: PluginDefaultTranslationsObject = { tagJobs: 'Завдання', tagUploads: 'Завантаження', tagAccess: 'Доступ', + tagReorder: 'Порядок', + tagPlugins: 'Плагіни', + tagPluginsDesc: 'Ендпоінти офіційних плагінів Payload.', + errorPlugin400: 'Некоректний запит', + errorPlugin401: 'Не автентифіковано', + errorPlugin403: 'Доступ заборонено', + errorPlugin404: 'Не знайдено', + errorPlugin500: 'Внутрішня помилка сервера', + ecommerceAddItem: 'Додати товар до кошика', + ecommerceRemoveItem: 'Видалити товар із кошика', + ecommerceUpdateItem: 'Змінити кількість товару в кошику', + ecommerceClearCart: 'Видалити всі товари з кошика', + ecommerceMergeCart: 'Об’єднати гостьовий кошик із цим кошиком', + ecommerceCartAccess: 'Дозволено власнику кошика або для гостьового кошика з його `secret` у тілі запиту.', + ecommerceCartResult: 'Оновлений кошик', + errorPluginEcommerce404: 'Кошик не знайдено або він недоступний', + ecommerceQuantity: 'Нова кількість або `{ "$inc": n }`, щоб змінити її на n.', + ecommerceInitiatePayment: 'Почати оплату через `{{method}}`', + ecommerceConfirmOrder: 'Підтвердити оплату через `{{method}}` і створити замовлення', + ecommercePaymentBody: + 'Використовує `cartID` (із `secret` для гостьового кошика) або кошик користувача. Без користувача обов’язковий `customerEmail`. Платіжному адаптеру можуть знадобитися інші поля.', + ecommerceInitiateResult: 'Оплату розпочато. Адаптер додає власні поля, наприклад client secret.', + ecommerceConfirmResult: 'Замовлення створено', + stripeWebhook: 'Отримати події вебхука Stripe', + stripeWebhookBody: + 'Необроблена подія Stripe, підписана в заголовку `Stripe-Signature`. Викликається Stripe, а не клієнтами API.', + stripeWebhookResult: 'Подію отримано', + errorPluginStripeWebhook400: 'Перевірка підпису не пройшла', + stripeRest: 'Викликати дозволений метод API Stripe', + stripeRestResult: 'Результат API Stripe', + errorPluginStripeRest404: 'API Stripe повернув помилку', + mcp: 'Надіслати повідомлення MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol через Streamable HTTP з JSON-відповідями. Анонімні запити працюють; перелік інструментів залежить від прав користувача. Клієнти з версією протоколу 2025 року мають надсилати `Accept: application/json, text/event-stream`.', + mcpResult: 'Відповідь JSON-RPC', + mcpOverrideAccess: 'Пропустити перевірки доступу. Лише для розробки.', + mcpGet: 'Не підтримується: сервер не відкриває потік подій', + errorPluginMcpGet405: 'Метод не дозволено, використовуйте POST', + mcpProtocolVersion: 'Узгоджена версія протоколу MCP, наприклад `2025-06-18`.', + mcpResult202: 'Прийнято: у тілі були лише сповіщення або відповіді', + errorPluginMcp404: 'Невідомий метод MCP', + errorPluginMcp406: 'У заголовку `Accept` немає `application/json` або `text/event-stream`', + errorPluginMcp413: 'Тіло запиту завелике', + errorPluginMcp415: '`Content-Type` має бути `application/json`', + seoTitle: 'Згенерувати мета-заголовок', + seoDescription: 'Згенерувати мета-опис', + seoUrl: 'Згенерувати URL попереднього перегляду', + seoImage: 'Згенерувати мета-зображення', + seoBody: + 'Документ, що редагується: `collectionSlug` або `globalSlug`, його `id` і поточні дані `doc`. Передається у вашу функцію генерації.', + seoResult: 'Згенероване значення. Порожній рядок, якщо функцію генерації не задано.', + searchReindex: 'Перебудувати пошуковий індекс для вибраних колекцій', + searchReindexResult: 'Підсумок переіндексації', + tenantOptions: 'Перелік орендарів, яких може вибрати користувач', + tenantOptionsResult: 'Варіанти орендарів', + exportDownload: 'Експортувати документи у файл', + exportDownloadResult: 'Файл експорту', + exportPreview: 'Попередній перегляд експорту', + importPreview: 'Попередній перегляд файлу імпорту', + previewResult: 'Сторінка документів попереднього перегляду', + importFileData: 'Вміст файлу в кодуванні base64.', + r2Upload: 'Завантажити файл у R2 частинами', + r2UploadDesc: + 'Три кроки на одному маршруті. Початок: передайте `collection`, `fileName` і `fileType`. Кожна частина: додайте `multipartId`, `multipartKey`, `multipartNumber` і `signedReceipt` та надішліть байти. Завершення: те саме без `multipartNumber`, зі списком частин у JSON.', + r2UploadResult: 'Завантаження розпочато, частину завантажено або завантаження завершено (ключ об’єкта текстом)', + errorStorageR2412: 'Файл із цим ключем уже існує', localizationHeading: 'Локалізація', localizationNote: diff --git a/src/shared/translations/locales/vi.ts b/src/shared/translations/locales/vi.ts index 6fae4c5..c1a921a 100644 --- a/src/shared/translations/locales/vi.ts +++ b/src/shared/translations/locales/vi.ts @@ -87,11 +87,25 @@ export const vi: PluginDefaultTranslationsObject = { uploadPayloadField: 'Các trường {{schema}} đã được chuyển thành chuỗi JSON. Ví dụ: `{"alt":"A caption"}`.', fileServe: 'Tệp', filePartial: 'Một phần của tệp, cho yêu cầu `Range`', + paramFileVersion: 'Trả về tệp được lưu với ID phiên bản này.', uploadInstructionsSummary: 'Lấy hướng dẫn tải tệp lên trước khi lưu tài liệu', uploadInstructionsResult: 'Nơi gửi các byte của tệp, và giá trị `file` cần gửi kèm yêu cầu tạo hoặc cập nhật', uploadStagePutSummary: 'Gửi các byte của tệp cho một lần tải lên tạm thời', uploadStageDeleteSummary: 'Xóa một lần tải lên tạm thời', uploadStageResult: 'Xong, không có nội dung', + paramPreferenceKey: 'Khóa của tùy chọn, ví dụ `nav`.', + preferenceGet: 'Lấy một tùy chọn của người dùng hiện tại', + preferenceGetResult: 'Tùy chọn đã lưu, hoặc `value: null` nếu không có', + preferenceSet: 'Lưu một tùy chọn của người dùng hiện tại', + preferenceSetBody: 'Gửi giá trị trong `value`. Body không có `value` sẽ được lưu nguyên vẹn làm giá trị.', + preferenceSetResult: 'Tùy chọn đã lưu', + preferenceDelete: 'Xóa một tùy chọn của người dùng hiện tại', + preferenceDeleteResult: 'Tùy chọn đã xóa', + reorder: 'Di chuyển tài liệu trong danh sách có thể sắp xếp', + reorderBody: + 'Di chuyển `docsToMove` ra sau `target` với `newKeyWillBe: "greater"`, hoặc ra trước với `"less"`. `orderableFieldName` là `_order` với collection có thể sắp xếp, hoặc trường thứ tự của join có thể sắp xếp. Khi không có `target.key`, Payload chỉ gán khóa thứ tự cho các tài liệu chưa có.', + reorderResult: 'Các khóa thứ tự mới, hoặc `message: "initial migration"` khi Payload gán khóa thứ tự lần đầu', + errorReorder400: 'Body yêu cầu không hợp lệ; lý do nằm trong `error`', error400: 'Lỗi xác thực hoặc lỗi truy vấn (ValidationError, QueryError)', error401: 'Chưa được xác thực (AuthenticationError)', @@ -115,6 +129,72 @@ export const vi: PluginDefaultTranslationsObject = { tagJobs: 'Jobs', tagUploads: 'Tải lên', tagAccess: 'Truy cập', + tagReorder: 'Sắp xếp lại', + tagPlugins: 'Plugin', + tagPluginsDesc: 'Các endpoint do plugin chính thức của Payload thêm vào.', + errorPlugin400: 'Yêu cầu không hợp lệ', + errorPlugin401: 'Chưa xác thực', + errorPlugin403: 'Bị cấm', + errorPlugin404: 'Không tìm thấy', + errorPlugin500: 'Lỗi máy chủ nội bộ', + ecommerceAddItem: 'Thêm sản phẩm vào giỏ hàng', + ecommerceRemoveItem: 'Xóa sản phẩm khỏi giỏ hàng', + ecommerceUpdateItem: 'Đổi số lượng của một sản phẩm trong giỏ', + ecommerceClearCart: 'Xóa mọi sản phẩm khỏi giỏ hàng', + ecommerceMergeCart: 'Gộp giỏ hàng khách vào giỏ hàng này', + ecommerceCartAccess: 'Cho phép chủ giỏ hàng, hoặc giỏ hàng khách có `secret` trong body.', + ecommerceCartResult: 'Giỏ hàng đã cập nhật', + errorPluginEcommerce404: 'Không tìm thấy giỏ hàng hoặc không thể truy cập', + ecommerceQuantity: 'Số lượng mới, hoặc `{ "$inc": n }` để thay đổi thêm n.', + ecommerceInitiatePayment: 'Bắt đầu thanh toán bằng `{{method}}`', + ecommerceConfirmOrder: 'Xác nhận thanh toán bằng `{{method}}` và tạo đơn hàng', + ecommercePaymentBody: + 'Dùng `cartID` (kèm `secret` với giỏ hàng khách) hoặc giỏ hàng của người dùng. Không có người dùng thì bắt buộc `customerEmail`. Adapter thanh toán có thể cần thêm trường.', + ecommerceInitiateResult: 'Đã bắt đầu thanh toán. Adapter thêm các trường riêng, ví dụ client secret.', + ecommerceConfirmResult: 'Đơn hàng đã được tạo', + stripeWebhook: 'Nhận sự kiện webhook từ Stripe', + stripeWebhookBody: + 'Sự kiện Stripe thô, được ký trong header `Stripe-Signature`. Do Stripe gọi, không phải client API.', + stripeWebhookResult: 'Đã nhận sự kiện', + errorPluginStripeWebhook400: 'Kiểm tra chữ ký thất bại', + stripeRest: 'Gọi một phương thức Stripe API được phép', + stripeRestResult: 'Kết quả từ Stripe API', + errorPluginStripeRest404: 'Stripe API trả về lỗi', + mcp: 'Gửi một thông điệp MCP JSON-RPC', + mcpDesc: + 'Model Context Protocol qua Streamable HTTP, phản hồi bằng JSON. Yêu cầu ẩn danh vẫn hoạt động; các công cụ được liệt kê tùy theo quyền của người dùng. Client dùng phiên bản giao thức năm 2025 phải gửi `Accept: application/json, text/event-stream`.', + mcpResult: 'Phản hồi JSON-RPC', + mcpOverrideAccess: 'Bỏ qua kiểm tra quyền truy cập. Chỉ dùng khi phát triển.', + mcpGet: 'Không hỗ trợ: máy chủ không mở luồng sự kiện', + errorPluginMcpGet405: 'Phương thức không được phép, hãy dùng POST', + mcpProtocolVersion: 'Phiên bản giao thức MCP đã thỏa thuận, ví dụ `2025-06-18`.', + mcpResult202: 'Đã chấp nhận: body chỉ chứa thông báo hoặc phản hồi', + errorPluginMcp404: 'Phương thức MCP không xác định', + errorPluginMcp406: 'Header `Accept` thiếu `application/json` hoặc `text/event-stream`', + errorPluginMcp413: 'Body của yêu cầu quá lớn', + errorPluginMcp415: '`Content-Type` phải là `application/json`', + seoTitle: 'Tạo tiêu đề meta', + seoDescription: 'Tạo mô tả meta', + seoUrl: 'Tạo URL xem trước', + seoImage: 'Tạo ảnh meta', + seoBody: + 'Tài liệu đang chỉnh sửa: `collectionSlug` hoặc `globalSlug`, `id` của nó và dữ liệu `doc` hiện tại. Được chuyển cho hàm generate của bạn.', + seoResult: 'Giá trị được tạo. Chuỗi rỗng khi chưa đặt hàm generate.', + searchReindex: 'Xây dựng lại chỉ mục tìm kiếm cho một số collection', + searchReindexResult: 'Tóm tắt lập chỉ mục lại', + tenantOptions: 'Liệt kê các tenant mà người dùng có thể chọn', + tenantOptionsResult: 'Tùy chọn tenant', + exportDownload: 'Xuất tài liệu ra tệp', + exportDownloadResult: 'Tệp xuất', + exportPreview: 'Xem trước bản xuất', + importPreview: 'Xem trước tệp nhập', + previewResult: 'Một trang tài liệu xem trước', + importFileData: 'Nội dung tệp, mã hóa base64.', + r2Upload: 'Tải tệp lên R2 theo từng phần', + r2UploadDesc: + 'Ba bước trên một route. Bắt đầu: gửi `collection`, `fileName` và `fileType`. Mỗi phần: thêm `multipartId`, `multipartKey`, `multipartNumber` và `signedReceipt`, rồi gửi các byte. Hoàn tất: như trên nhưng không có `multipartNumber`, kèm danh sách JSON các phần.', + r2UploadResult: 'Đã bắt đầu tải lên, đã tải lên một phần hoặc đã hoàn tất (khóa đối tượng dạng văn bản)', + errorStorageR2412: 'Đã có tệp tại khóa này', localizationHeading: 'Đa ngôn ngữ', localizationNote: diff --git a/src/shared/translations/locales/zh.ts b/src/shared/translations/locales/zh.ts index b872511..7ca97f1 100644 --- a/src/shared/translations/locales/zh.ts +++ b/src/shared/translations/locales/zh.ts @@ -83,11 +83,25 @@ export const zh: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON 字符串化的 {{schema}} 字段。示例:`{"alt":"A caption"}`。', fileServe: '文件', filePartial: '`Range` 请求返回的部分文件', + paramFileVersion: '返回以此版本 ID 保存的文件。', uploadInstructionsSummary: '在保存文档前获取上传文件的说明', uploadInstructionsResult: '文件字节的发送位置,以及在创建或更新请求中发送的 `file` 值', uploadStagePutSummary: '为临时上传发送文件字节', uploadStageDeleteSummary: '删除临时上传', uploadStageResult: '完成,无内容', + paramPreferenceKey: '偏好设置的键,例如 `nav`。', + preferenceGet: '获取当前用户的偏好设置', + preferenceGetResult: '已保存的偏好设置;不存在时为 `value: null`', + preferenceSet: '保存当前用户的偏好设置', + preferenceSetBody: '在 `value` 中发送值。不含 `value` 的请求体会整体作为值保存。', + preferenceSetResult: '已保存的偏好设置', + preferenceDelete: '删除当前用户的偏好设置', + preferenceDeleteResult: '已删除的偏好设置', + reorder: '在可排序列表中移动文档', + reorderBody: + '`newKeyWillBe: "greater"` 时将 `docsToMove` 移到 `target` 之后,`"less"` 时移到其之前。对于可排序集合,`orderableFieldName` 为 `_order`;对于可排序 join,则为其排序字段。没有 `target.key` 时,Payload 只会为尚无排序键的文档分配排序键。', + reorderResult: '新的排序键;Payload 首次设置排序键时为 `message: "initial migration"`', + errorReorder400: '请求体无效;原因见 `error`', error400: '验证或查询错误(ValidationError、QueryError)', error401: '未认证(AuthenticationError)', @@ -110,6 +124,70 @@ export const zh: PluginDefaultTranslationsObject = { tagJobs: '作业', tagUploads: '上传', tagAccess: '访问', + tagReorder: '排序', + tagPlugins: '插件', + tagPluginsDesc: 'Payload 官方插件添加的端点。', + errorPlugin400: '请求无效', + errorPlugin401: '未认证', + errorPlugin403: '禁止访问', + errorPlugin404: '未找到', + errorPlugin500: '服务器内部错误', + ecommerceAddItem: '向购物车添加商品', + ecommerceRemoveItem: '从购物车移除商品', + ecommerceUpdateItem: '修改购物车商品数量', + ecommerceClearCart: '清空购物车中的所有商品', + ecommerceMergeCart: '将访客购物车合并到此购物车', + ecommerceCartAccess: '允许购物车所有者访问,或在请求体中带上 `secret` 的访客购物车。', + ecommerceCartResult: '更新后的购物车', + errorPluginEcommerce404: '购物车不存在或无权访问', + ecommerceQuantity: '新数量,或用 `{ "$inc": n }` 增减 n。', + ecommerceInitiatePayment: '使用 `{{method}}` 发起支付', + ecommerceConfirmOrder: '使用 `{{method}}` 确认支付并创建订单', + ecommercePaymentBody: + '使用 `cartID`(访客购物车需带 `secret`)或用户的购物车。没有用户时 `customerEmail` 必填。支付适配器可能需要更多字段。', + ecommerceInitiateResult: '支付已发起。适配器会添加自己的字段,例如 client secret。', + ecommerceConfirmResult: '订单已创建', + stripeWebhook: '接收 Stripe Webhook 事件', + stripeWebhookBody: '原始 Stripe 事件,在 `Stripe-Signature` 请求头中签名。由 Stripe 调用,而不是 API 客户端。', + stripeWebhookResult: '已收到事件', + errorPluginStripeWebhook400: '签名校验失败', + stripeRest: '调用允许的 Stripe API 方法', + stripeRestResult: 'Stripe API 的结果', + errorPluginStripeRest404: 'Stripe API 返回了错误', + mcp: '发送 MCP JSON-RPC 消息', + mcpDesc: + '基于 Streamable HTTP 的 Model Context Protocol,以 JSON 响应。支持匿名请求;列出的工具取决于用户权限。使用 2025 年协议版本的客户端必须发送 `Accept: application/json, text/event-stream`。', + mcpResult: 'JSON-RPC 响应', + mcpOverrideAccess: '跳过访问检查。仅用于开发。', + mcpGet: '不支持:服务器不会打开事件流', + errorPluginMcpGet405: '方法不允许,请使用 POST', + mcpProtocolVersion: '协商的 MCP 协议版本,例如 `2025-06-18`。', + mcpResult202: '已接受:请求体只包含通知或响应', + errorPluginMcp404: '未知的 MCP 方法', + errorPluginMcp406: '`Accept` 请求头缺少 `application/json` 或 `text/event-stream`', + errorPluginMcp413: '请求体过大', + errorPluginMcp415: '`Content-Type` 必须是 `application/json`', + seoTitle: '生成 meta 标题', + seoDescription: '生成 meta 描述', + seoUrl: '生成预览 URL', + seoImage: '生成 meta 图片', + seoBody: '正在编辑的文档:`collectionSlug` 或 `globalSlug`、其 `id` 以及当前的 `doc` 数据。会传给你的生成函数。', + seoResult: '生成的值。未设置生成函数时为空字符串。', + searchReindex: '为部分集合重建搜索索引', + searchReindexResult: '重建索引摘要', + tenantOptions: '列出用户可选择的租户', + tenantOptionsResult: '租户选项', + exportDownload: '将文档导出到文件', + exportDownloadResult: '导出文件', + exportPreview: '预览导出', + importPreview: '预览导入文件', + previewResult: '一页预览文档', + importFileData: '文件内容,base64 编码。', + r2Upload: '分片上传文件到 R2', + r2UploadDesc: + '同一路由分三步。开始:发送 `collection`、`fileName` 和 `fileType`。每个分片:加上 `multipartId`、`multipartKey`、`multipartNumber` 和 `signedReceipt`,并发送字节。完成:同样的参数但不含 `multipartNumber`,并附上分片的 JSON 列表。', + r2UploadResult: '上传已开始、分片已上传或上传已完成(以文本返回对象键)', + errorStorageR2412: '该键已存在文件', localizationHeading: '本地化', localizationNote: diff --git a/src/shared/translations/locales/zhTw.ts b/src/shared/translations/locales/zhTw.ts index 69ecbf0..88d1e57 100644 --- a/src/shared/translations/locales/zhTw.ts +++ b/src/shared/translations/locales/zhTw.ts @@ -83,11 +83,25 @@ export const zhTw: PluginDefaultTranslationsObject = { uploadPayloadField: 'JSON 字串化的 {{schema}} 欄位。範例:`{"alt":"A caption"}`。', fileServe: '檔案', filePartial: '`Range` 請求傳回的部分檔案', + paramFileVersion: '傳回以此版本 ID 儲存的檔案。', uploadInstructionsSummary: '在儲存文件前取得上傳檔案的說明', uploadInstructionsResult: '檔案位元組的傳送位置,以及在建立或更新請求中傳送的 `file` 值', uploadStagePutSummary: '為暫存上傳傳送檔案位元組', uploadStageDeleteSummary: '刪除暫存上傳', uploadStageResult: '完成,無內容', + paramPreferenceKey: '偏好設定的鍵,例如 `nav`。', + preferenceGet: '取得目前使用者的偏好設定', + preferenceGetResult: '已儲存的偏好設定;不存在時為 `value: null`', + preferenceSet: '儲存目前使用者的偏好設定', + preferenceSetBody: '在 `value` 中傳送值。不含 `value` 的請求內容會整體作為值儲存。', + preferenceSetResult: '已儲存的偏好設定', + preferenceDelete: '刪除目前使用者的偏好設定', + preferenceDeleteResult: '已刪除的偏好設定', + reorder: '在可排序清單中移動文件', + reorderBody: + '`newKeyWillBe: "greater"` 時將 `docsToMove` 移到 `target` 之後,`"less"` 時移到其之前。對於可排序集合,`orderableFieldName` 為 `_order`;對於可排序 join,則為其排序欄位。沒有 `target.key` 時,Payload 只會為尚無排序鍵的文件指派排序鍵。', + reorderResult: '新的排序鍵;Payload 首次設定排序鍵時為 `message: "initial migration"`', + errorReorder400: '請求內容無效;原因見 `error`', error400: '驗證或查詢錯誤 (ValidationError, QueryError)', error401: '未驗證 (AuthenticationError)', @@ -110,6 +124,70 @@ export const zhTw: PluginDefaultTranslationsObject = { tagJobs: '工作', tagUploads: '上傳', tagAccess: '存取', + tagReorder: '排序', + tagPlugins: '外掛程式', + tagPluginsDesc: 'Payload 官方外掛程式新增的端點。', + errorPlugin400: '請求無效', + errorPlugin401: '未驗證', + errorPlugin403: '禁止存取', + errorPlugin404: '找不到', + errorPlugin500: '伺服器內部錯誤', + ecommerceAddItem: '將商品加入購物車', + ecommerceRemoveItem: '從購物車移除商品', + ecommerceUpdateItem: '變更購物車商品數量', + ecommerceClearCart: '移除購物車中的所有商品', + ecommerceMergeCart: '將訪客購物車合併到此購物車', + ecommerceCartAccess: '允許購物車擁有者存取,或在請求內容中帶有 `secret` 的訪客購物車。', + ecommerceCartResult: '更新後的購物車', + errorPluginEcommerce404: '找不到購物車或無法存取', + ecommerceQuantity: '新數量,或用 `{ "$inc": n }` 增減 n。', + ecommerceInitiatePayment: '使用 `{{method}}` 開始付款', + ecommerceConfirmOrder: '使用 `{{method}}` 確認付款並建立訂單', + ecommercePaymentBody: + '使用 `cartID`(訪客購物車需帶 `secret`)或使用者的購物車。沒有使用者時 `customerEmail` 為必填。付款轉接器可能需要更多欄位。', + ecommerceInitiateResult: '付款已開始。轉接器會加入自己的欄位,例如 client secret。', + ecommerceConfirmResult: '訂單已建立', + stripeWebhook: '接收 Stripe Webhook 事件', + stripeWebhookBody: '原始 Stripe 事件,在 `Stripe-Signature` 標頭中簽署。由 Stripe 呼叫,而非 API 用戶端。', + stripeWebhookResult: '已收到事件', + errorPluginStripeWebhook400: '簽章驗證失敗', + stripeRest: '呼叫允許的 Stripe API 方法', + stripeRestResult: 'Stripe API 的結果', + errorPluginStripeRest404: 'Stripe API 回傳錯誤', + mcp: '傳送 MCP JSON-RPC 訊息', + mcpDesc: + '透過 Streamable HTTP 的 Model Context Protocol,以 JSON 回應。支援匿名請求;列出的工具取決於使用者權限。使用 2025 年協定版本的用戶端必須傳送 `Accept: application/json, text/event-stream`。', + mcpResult: 'JSON-RPC 回應', + mcpOverrideAccess: '略過存取檢查。僅供開發使用。', + mcpGet: '不支援:伺服器不會開啟事件串流', + errorPluginMcpGet405: '不允許此方法,請使用 POST', + mcpProtocolVersion: '協商的 MCP 協定版本,例如 `2025-06-18`。', + mcpResult202: '已接受:請求內容只包含通知或回應', + errorPluginMcp404: '未知的 MCP 方法', + errorPluginMcp406: '`Accept` 標頭缺少 `application/json` 或 `text/event-stream`', + errorPluginMcp413: '請求內容過大', + errorPluginMcp415: '`Content-Type` 必須是 `application/json`', + seoTitle: '產生 meta 標題', + seoDescription: '產生 meta 描述', + seoUrl: '產生預覽 URL', + seoImage: '產生 meta 圖片', + seoBody: '正在編輯的文件:`collectionSlug` 或 `globalSlug`、其 `id` 以及目前的 `doc` 資料。會傳給你的產生函式。', + seoResult: '產生的值。未設定產生函式時為空字串。', + searchReindex: '為部分集合重建搜尋索引', + searchReindexResult: '重建索引摘要', + tenantOptions: '列出使用者可選擇的租戶', + tenantOptionsResult: '租戶選項', + exportDownload: '將文件匯出成檔案', + exportDownloadResult: '匯出檔案', + exportPreview: '預覽匯出', + importPreview: '預覽匯入檔案', + previewResult: '一頁預覽文件', + importFileData: '檔案內容,base64 編碼。', + r2Upload: '分段上傳檔案到 R2', + r2UploadDesc: + '同一路由分三步。開始:傳送 `collection`、`fileName` 和 `fileType`。每個分段:加上 `multipartId`、`multipartKey`、`multipartNumber` 和 `signedReceipt`,並傳送位元組。完成:相同參數但不含 `multipartNumber`,並附上分段的 JSON 清單。', + r2UploadResult: '上傳已開始、分段已上傳或上傳已完成(以文字回傳物件鍵)', + errorStorageR2412: '此鍵已存在檔案', localizationHeading: '本地化', localizationNote: diff --git a/src/shared/translations/types.ts b/src/shared/translations/types.ts index f2fcb3b..5328457 100644 --- a/src/shared/translations/types.ts +++ b/src/shared/translations/types.ts @@ -79,11 +79,24 @@ export type PluginDefaultTranslationsObject = { uploadPayloadField: string fileServe: string filePartial: string + paramFileVersion: string uploadInstructionsSummary: string uploadInstructionsResult: string uploadStagePutSummary: string uploadStageDeleteSummary: string uploadStageResult: string + paramPreferenceKey: string + preferenceGet: string + preferenceGetResult: string + preferenceSet: string + preferenceSetBody: string + preferenceSetResult: string + preferenceDelete: string + preferenceDeleteResult: string + reorder: string + reorderBody: string + reorderResult: string + errorReorder400: string error400: string error401: string @@ -105,6 +118,67 @@ export type PluginDefaultTranslationsObject = { tagJobs: string tagUploads: string tagAccess: string + tagReorder: string + tagPlugins: string + tagPluginsDesc: string + errorPlugin400: string + errorPlugin401: string + errorPlugin403: string + errorPlugin404: string + errorPlugin500: string + ecommerceAddItem: string + ecommerceRemoveItem: string + ecommerceUpdateItem: string + ecommerceClearCart: string + ecommerceMergeCart: string + ecommerceCartAccess: string + ecommerceCartResult: string + errorPluginEcommerce404: string + ecommerceQuantity: string + ecommerceInitiatePayment: string + ecommerceConfirmOrder: string + ecommercePaymentBody: string + ecommerceInitiateResult: string + ecommerceConfirmResult: string + stripeWebhook: string + stripeWebhookBody: string + stripeWebhookResult: string + errorPluginStripeWebhook400: string + stripeRest: string + stripeRestResult: string + errorPluginStripeRest404: string + mcp: string + mcpDesc: string + mcpResult: string + mcpOverrideAccess: string + mcpGet: string + errorPluginMcpGet405: string + mcpProtocolVersion: string + mcpResult202: string + errorPluginMcp404: string + errorPluginMcp406: string + errorPluginMcp413: string + errorPluginMcp415: string + seoTitle: string + seoDescription: string + seoUrl: string + seoImage: string + seoBody: string + seoResult: string + searchReindex: string + searchReindexResult: string + tenantOptions: string + tenantOptionsResult: string + exportDownload: string + exportDownloadResult: string + exportPreview: string + importPreview: string + previewResult: string + importFileData: string + r2Upload: string + r2UploadDesc: string + r2UploadResult: string + errorStorageR2412: string localizationHeading: string localizationNote: string diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index f172e76..3067b8f 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -17,7 +17,7 @@ export type OpenApiVersion = '3.0' | '3.1' | '3.2' export type EntityKind = 'collection' | 'global' -export type OperationKind = EntityKind | 'custom' | 'jobs' | 'system' +export type OperationKind = EntityKind | 'custom' | 'jobs' | 'system' | 'plugin' export type HttpMethod = 'get' | 'post' | 'patch' | 'put' | 'delete' @@ -130,24 +130,38 @@ export type EntityMatcher = string | RegExp | { kind: EntityKind; slug: string } * an operation is removed if any rule matches it. Fields: * - `method` — HTTP method(s); omit to match any. * - `slug` — collection or global slug, exact string or RegExp; omit to match any. - * A rule with `slug` never matches custom, jobs or system operations. + * A rule with `slug` never matches custom, jobs or system operations, or plugin + * operations mounted at the API root. * - `kind` — restrict to one operation group; omit to match all. * - `path` — RegExp tested against the final route path (e.g. `/api/posts/{id}`). + * - `plugin` — slug of the plugin that adds the operation (e.g. `'@payloadcms/plugin-seo'`), + * exact string or RegExp; omit to match any. A rule with `plugin` never matches an + * operation without one. */ export interface OperationRule { method?: HttpMethod | HttpMethod[] slug?: string | RegExp kind?: OperationKind path?: RegExp + plugin?: string | RegExp } /** Context passed to `excludeOperations` functions and `security` for each operation. */ export interface OperationContext { method: HttpMethod path: string - /** Collection or global slug. Unset for custom, jobs and system operations. */ + /** + * Collection or global slug. For plugin operations, the collection the plugin + * mounts them on. Unset for custom, jobs and system operations. + */ slug?: string kind: OperationKind + /** + * Slug of the plugin that adds the operation: an official Payload plugin + * (kind `'plugin'`), or the `x-payload-plugin` value of a custom endpoint's + * `custom.openapi`. + */ + plugin?: string } /** A rule, or a function that drops the operation when it returns (or resolves to) `true`. */ @@ -216,16 +230,16 @@ export interface FilterOptions { */ includeJobs?: boolean /** - * Drops operations from every group: collection, global, custom, jobs and - * system. An operation is removed when any rule matches it or any function - * returns `true`. + * Drops operations from every group: collection, global, custom, jobs, + * system and plugin. An operation is removed when any rule matches it or any + * function returns `true`. * @default [] */ excludeOperations?: OperationFilter[] } export interface OpenApiExtension { - /** Paths deep-merged into the generated ones. */ + /** Paths merged into the generated ones. An operation here replaces the generated operation with the same path and method. */ paths?: PathsObject /** Components deep-merged into the generated ones. */ components?: ComponentsObject diff --git a/tests/app/(payload)/admin/importMap.js b/tests/app/(payload)/admin/importMap.js index 2efdd32..1c6216c 100644 --- a/tests/app/(payload)/admin/importMap.js +++ b/tests/app/(payload)/admin/importMap.js @@ -1,16 +1,98 @@ +import { LLMInstructionsMenuItem as LLMInstructionsMenuItem_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { OverviewComponent as OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client' +import { MetaTitleComponent as MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client' +import { MetaDescriptionComponent as MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client' +import { MetaImageComponent as MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client' +import { PreviewComponent as PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client' +import { ExportListMenuItem as ExportListMenuItem_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { ImportListMenuItem as ImportListMenuItem_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { NullField as NullField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { HierarchyButton as HierarchyButton_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { HierarchyTypeFieldServer as HierarchyTypeFieldServer_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { WatchTenantCollection as WatchTenantCollection_1d0591e3cf4f332c83a86da13a0de59a } from '@payloadcms/plugin-multi-tenant/client' +import { LinkToDoc as LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634 } from '@payloadcms/plugin-search/client' +import { ReindexButton as ReindexButton_aead06e4cbf6b2620c5c51c9ab283634 } from '@payloadcms/plugin-search/client' +import { FormatField as FormatField_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { LimitField as LimitField_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { Page as Page_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { SortBy as SortBy_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { SortOrder as SortOrder_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { LocaleField as LocaleField_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { SelectionToUseField as SelectionToUseField_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { FieldsToExport as FieldsToExport_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { CollectionField as CollectionField_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { ExportPreview as ExportPreview_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { ExportSaveButton as ExportSaveButton_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { ImportPreview as ImportPreview_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { ImportSaveButton as ImportSaveButton_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { VariantOptionsSelector as VariantOptionsSelector_b91672ccd6e8b071c11142ab941fedfb } from '@payloadcms/plugin-ecommerce/rsc' +import { PriceCell as PriceCell_e27bf7b8cc50640dcdd584767b8eac3c } from '@payloadcms/plugin-ecommerce/client' +import { PriceInput as PriceInput_b91672ccd6e8b071c11142ab941fedfb } from '@payloadcms/plugin-ecommerce/rsc' +import { LLMInstructionsDescription as LLMInstructionsDescription_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { LLMInstructionsCell as LLMInstructionsCell_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { TenantSelector as TenantSelector_d6d5f193a167989e2ee7d14202901e62 } from '@payloadcms/plugin-multi-tenant/rsc' +import { TenantSelectionProvider as TenantSelectionProvider_d6d5f193a167989e2ee7d14202901e62 } from '@payloadcms/plugin-multi-tenant/rsc' +import { ImportExportProvider as ImportExportProvider_cdf7e044479f899a31f804427d568b36 } from '@payloadcms/plugin-import-export/rsc' +import { R2ClientUploadHandler as R2ClientUploadHandler_85cc02ed84006fcc91d3aff39dda669d } from '@payloadcms/storage-r2/client' +import { FolderIcon as FolderIcon_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { HierarchySidebarTabServer as HierarchySidebarTabServer_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { WelcomeWidget as WelcomeWidget_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' import { CollectionCards as CollectionCards_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' import { CollectionQueryWidget as CollectionQueryWidget_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' import { QueryPresetsWhereField as QueryPresetsWhereField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' import { CollectionQuerySortField as CollectionQuerySortField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' import { RecentlyViewedWidget as RecentlyViewedWidget_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' import { RecentlyViewedCollectionsField as RecentlyViewedCollectionsField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' +import { UploadDropzoneWidget as UploadDropzoneWidget_ab83ff7e88da8d3530831f296ec4756a } from '@payloadcms/ui/rsc' +import { UploadDropzoneCollectionsField as UploadDropzoneCollectionsField_3817bf644402e67bfe6577f60ef982de } from '@payloadcms/ui' /** @type import('payload').ImportMap */ export const importMap = { + "@payloadcms/ui#LLMInstructionsMenuItem": LLMInstructionsMenuItem_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860, + "@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860, + "@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860, + "@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860, + "@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860, + "@payloadcms/plugin-import-export/rsc#ExportListMenuItem": ExportListMenuItem_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#ImportListMenuItem": ImportListMenuItem_cdf7e044479f899a31f804427d568b36, + "@payloadcms/ui#NullField": NullField_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/ui/rsc#HierarchyButton": HierarchyButton_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/ui/rsc#HierarchyTypeFieldServer": HierarchyTypeFieldServer_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/plugin-multi-tenant/client#WatchTenantCollection": WatchTenantCollection_1d0591e3cf4f332c83a86da13a0de59a, + "@payloadcms/plugin-search/client#LinkToDoc": LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634, + "@payloadcms/plugin-search/client#ReindexButton": ReindexButton_aead06e4cbf6b2620c5c51c9ab283634, + "@payloadcms/plugin-import-export/rsc#FormatField": FormatField_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#LimitField": LimitField_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#Page": Page_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#SortBy": SortBy_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#SortOrder": SortOrder_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#LocaleField": LocaleField_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#SelectionToUseField": SelectionToUseField_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#FieldsToExport": FieldsToExport_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#CollectionField": CollectionField_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#ExportPreview": ExportPreview_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#ExportSaveButton": ExportSaveButton_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#ImportPreview": ImportPreview_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-import-export/rsc#ImportSaveButton": ImportSaveButton_cdf7e044479f899a31f804427d568b36, + "@payloadcms/plugin-ecommerce/rsc#VariantOptionsSelector": VariantOptionsSelector_b91672ccd6e8b071c11142ab941fedfb, + "@payloadcms/plugin-ecommerce/client#PriceCell": PriceCell_e27bf7b8cc50640dcdd584767b8eac3c, + "@payloadcms/plugin-ecommerce/rsc#PriceInput": PriceInput_b91672ccd6e8b071c11142ab941fedfb, + "@payloadcms/ui#LLMInstructionsDescription": LLMInstructionsDescription_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/ui/rsc#LLMInstructionsCell": LLMInstructionsCell_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/plugin-multi-tenant/rsc#TenantSelector": TenantSelector_d6d5f193a167989e2ee7d14202901e62, + "@payloadcms/plugin-multi-tenant/rsc#TenantSelectionProvider": TenantSelectionProvider_d6d5f193a167989e2ee7d14202901e62, + "@payloadcms/plugin-import-export/rsc#ImportExportProvider": ImportExportProvider_cdf7e044479f899a31f804427d568b36, + "@payloadcms/storage-r2/client#R2ClientUploadHandler": R2ClientUploadHandler_85cc02ed84006fcc91d3aff39dda669d, + "@payloadcms/ui#FolderIcon": FolderIcon_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/ui/rsc#HierarchySidebarTabServer": HierarchySidebarTabServer_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/ui/rsc#WelcomeWidget": WelcomeWidget_ab83ff7e88da8d3530831f296ec4756a, "@payloadcms/ui/rsc#CollectionCards": CollectionCards_ab83ff7e88da8d3530831f296ec4756a, "@payloadcms/ui/rsc#CollectionQueryWidget": CollectionQueryWidget_ab83ff7e88da8d3530831f296ec4756a, "@payloadcms/ui#QueryPresetsWhereField": QueryPresetsWhereField_3817bf644402e67bfe6577f60ef982de, "@payloadcms/ui#CollectionQuerySortField": CollectionQuerySortField_3817bf644402e67bfe6577f60ef982de, "@payloadcms/ui/rsc#RecentlyViewedWidget": RecentlyViewedWidget_ab83ff7e88da8d3530831f296ec4756a, - "@payloadcms/ui#RecentlyViewedCollectionsField": RecentlyViewedCollectionsField_3817bf644402e67bfe6577f60ef982de + "@payloadcms/ui#RecentlyViewedCollectionsField": RecentlyViewedCollectionsField_3817bf644402e67bfe6577f60ef982de, + "@payloadcms/ui/rsc#UploadDropzoneWidget": UploadDropzoneWidget_ab83ff7e88da8d3530831f296ec4756a, + "@payloadcms/ui#UploadDropzoneCollectionsField": UploadDropzoneCollectionsField_3817bf644402e67bfe6577f60ef982de } diff --git a/tests/collections/Tenants.ts b/tests/collections/Tenants.ts new file mode 100644 index 0000000..7bd81bc --- /dev/null +++ b/tests/collections/Tenants.ts @@ -0,0 +1,7 @@ +import type { CollectionConfig } from 'payload' + +export const Tenants: CollectionConfig = { + slug: 'tenants', + admin: { useAsTitle: 'name' }, + fields: [{ name: 'name', type: 'text', required: true }], +} diff --git a/tests/collections/index.ts b/tests/collections/index.ts index 74fa60c..653c6ac 100644 --- a/tests/collections/index.ts +++ b/tests/collections/index.ts @@ -3,3 +3,4 @@ export { Media } from './Media.js' export { Posts } from './Posts.js' export { Tags } from './Tags.js' export { Users } from './Users.js' +export { Tenants } from './Tenants.js' diff --git a/tests/integration/spec.int.spec.ts b/tests/integration/spec.int.spec.ts index 3dd649e..2b219cb 100644 --- a/tests/integration/spec.int.spec.ts +++ b/tests/integration/spec.int.spec.ts @@ -351,13 +351,23 @@ describe('generated document', () => { it('documents the file and upload instructions endpoints for upload collections', () => { expect(doc.paths['/api/media/file/{filename}']?.get).toBeDefined() expect(doc.paths['/api/users/file/{filename}']).toBeUndefined() + expect(doc.paths['/api/media/{id}/rename']?.post).toBeDefined() + expect(doc.paths['/api/users/{id}/rename']).toBeUndefined() const body = doc.paths['/api/upload-instructions']?.post?.requestBody as Body const bodySchema = body.content['application/json']!.schema as Schema const slug = bodySchema.properties?.collectionSlug as Schema - expect(slug.enum).toEqual(['media']) + expect(slug.enum).toEqual(['media', 'exports', 'imports']) expect(doc.paths['/api/upload-instructions/{uploadId}']?.put).toBeDefined() expect(doc.paths['/api/upload-instructions/{uploadId}']?.delete).toBeDefined() }) + + it('documents `/reorder` for the target of an orderable join', () => { + const body = doc.paths['/api/reorder']?.post?.requestBody as Body + const bodySchema = body.content['application/json']!.schema as Schema + const slug = bodySchema.properties?.collectionSlug as Schema + expect(slug.enum).toEqual(['variantOptions']) + expect(doc.paths['/api/reorder']?.post?.security).toEqual([{ PayloadToken: [] }]) + }) }) describe('query params', () => { @@ -706,6 +716,50 @@ describe('generated document', () => { }) }) + describe('official plugins', () => { + it.each([ + ['/api/plugin-seo/generate-title', 'post', 'SEO', true], + ['/api/plugin-seo/generate-image', 'post', 'SEO', true], + ['/api/search/reindex', 'post', 'Search', true], + ['/api/tenants/populate-tenant-options', 'get', 'Multi-tenant', true], + ['/api/exports/download', 'post', 'Import/Export', true], + ['/api/exports/export-preview', 'post', 'Import/Export', false], + ['/api/imports/preview-data', 'post', 'Import/Export', false], + ['/api/mcp', 'post', 'MCP', false], + ['/api/mcp', 'get', 'MCP', false], + ['/api/stripe/webhooks', 'post', 'Stripe', false], + ['/api/stripe/rest', 'post', 'Stripe', true], + ['/api/carts/{id}/add-item', 'post', 'Ecommerce', false], + ['/api/carts/{id}/merge', 'post', 'Ecommerce', true], + ['/api/payments/stripe/initiate', 'post', 'Ecommerce', false], + ['/api/payments/stripe/confirm-order', 'post', 'Ecommerce', false], + ['/api/payments/stripe/webhooks', 'post', 'Ecommerce', false], + ['/api/storage-r2-multi-part-upload', 'post', 'Storage R2', true], + ] as const)('documents %s %s under the %s tag', (path, method, tag, isSecured) => { + const operation = doc.paths[path]?.[method] + expect(operation?.tags).toEqual([tag]) + expect(Boolean(operation?.security)).toBe(isSecured) + }) + + it('reads options from the installed plugin', () => { + const body = doc.paths['/api/search/reindex']?.post?.requestBody as Body + const bodySchema = body.content['application/json']!.schema as Schema + expect(bodySchema.properties?.collections).toMatchObject({ items: { enum: ['posts'] } }) + }) + + it('groups the plugin tags under Plugins when `nestedTags` is on', () => { + const tags: TagObject[] = nestedDoc.tags ?? [] + const children = tags.filter((tag) => tag.parent === 'Plugins').map((tag) => tag.name) + expect(tags.find((tag) => tag.name === 'Plugins')?.kind).toBe('nav') + expect(children.toSorted()).toEqual( + ['Ecommerce', 'Import/Export', 'MCP', 'Multi-tenant', 'SEO', 'Storage R2', 'Stripe'].toSorted(), + ) + const names = tags.map((tag) => tag.name) + expect(names).toHaveLength(new Set(names).size) + expect(tags.find((tag) => tag.name === 'Search')?.parent).toBe('Collections') + }) + }) + describe('doc languages', () => { it('advertises supported languages in the description and `x-doc-languages`', () => { expect((doc as Record)['x-doc-languages']).toEqual(['en', 'de', 'fr']) diff --git a/tests/payload.config.ts b/tests/payload.config.ts index 060908e..f0f4bde 100644 --- a/tests/payload.config.ts +++ b/tests/payload.config.ts @@ -1,23 +1,35 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' +import { ecommercePlugin } from '@payloadcms/plugin-ecommerce' +import { stripeAdapter } from '@payloadcms/plugin-ecommerce/payments/stripe' +import { importExportPlugin } from '@payloadcms/plugin-import-export' +import { mcpPlugin } from '@payloadcms/plugin-mcp' +import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant' +import { searchPlugin } from '@payloadcms/plugin-search' +import { seoPlugin } from '@payloadcms/plugin-seo' +import { stripePlugin } from '@payloadcms/plugin-stripe' +import { r2Storage } from '@payloadcms/storage-r2' import { de } from '@payloadcms/translations/languages/de' import { en } from '@payloadcms/translations/languages/en' import { fr } from '@payloadcms/translations/languages/fr' import { testDatabase } from '@seshuk/payload-plugin-tooling/test-database' -import { buildConfig } from 'payload' +import { type Access, buildConfig } from 'payload' import { openapi, scalar, swaggerUi } from '@/index.js' import { CallToAction } from './blocks/CallToAction.js' import { MediaBlock } from './blocks/MediaBlock.js' -import { Folders, Media, Posts, Tags, Users } from './collections/index.js' +import { Folders, Media, Posts, Tags, Tenants, Users } from './collections/index.js' import { Settings } from './globals/Settings.js' const dirname = path.dirname(fileURLToPath(import.meta.url)) const devUser = { email: 'dev@example.com', password: 'dev' } +const loggedIn: Access = ({ req }) => Boolean(req.user) +const stripeSecretKey = process.env.STRIPE_SECRET_KEY ?? '' + const buildConfigAsync = async () => buildConfig({ secret: process.env.PAYLOAD_SECRET || 'dev-secret', @@ -43,7 +55,7 @@ const buildConfigAsync = async () => }, ], }, - collections: [Users, Posts, Media, Tags, Folders], + collections: [Users, Posts, Media, Tags, Folders, Tenants], globals: [Settings], plugins: [ openapi({ @@ -54,7 +66,33 @@ const buildConfigAsync = async () => }), scalar(), swaggerUi({ path: '/swagger' }), + seoPlugin({ collections: ['posts'], uploadsCollection: 'media' }), + searchPlugin({ collections: ['posts'] }), + multiTenantPlugin({ collections: {}, tenantsSlug: 'tenants' }), + importExportPlugin({ collections: [{ slug: 'posts' }] }), + mcpPlugin({}), + stripePlugin({ stripeSecretKey, rest: { allowedMethods: ['customers.list'] } }), + ecommercePlugin({ + access: { + adminOnlyFieldAccess: ({ req }) => Boolean(req.user), + adminOrPublishedStatus: () => true, + isAdmin: loggedIn, + isDocumentOwner: loggedIn, + }, + customers: { slug: 'users' }, + products: true, + payments: { + paymentMethods: [ + stripeAdapter({ + publishableKey: process.env.STRIPE_PUBLISHABLE_KEY ?? '', + secretKey: stripeSecretKey, + webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, + }), + ], + }, + }), ], + storage: [r2Storage({ bucket: {} as never, collections: { media: true }, clientUploads: true })], onInit: async (payload) => { const existing = await payload.find({ collection: 'users', diff --git a/tests/unit/index.spec.ts b/tests/unit/index.spec.ts index 70ea102..7f584ce 100644 --- a/tests/unit/index.spec.ts +++ b/tests/unit/index.spec.ts @@ -591,6 +591,80 @@ describe('spec/buildDocument', () => { expect(open.paths?.['/api/payload-jobs/run']?.get?.security).toBeUndefined() }) }) + + describe('reorder', () => { + const reorderEndpoint = { path: '/reorder', method: 'post', handler: () => new Response() } + const orderable = [ + coll('posts', { orderable: true, fields: [] }), + coll('tags', { fields: [] }), + coll('options', { fields: [] }), + coll('types', { + fields: [], + joins: { options: [{ field: { orderable: true } }] } as unknown as SanitizedCollectionConfig['joins'], + }), + coll('secret', { orderable: true, fields: [] }), + ] + const build = (endpoints: unknown[], filters = {}) => + buildDocument( + baseInput({ + config: { endpoints } as unknown as SanitizedConfig, + collections: orderable, + options: resolveOptions({ info: { title: 'T', version: '1.0.0' }, nestedTags: true, filters }), + }), + ) + + it('documents `/reorder` only when Payload mounted it', async () => { + expect((await build([])).paths?.['/api/reorder']).toBeUndefined() + const doc = await build([reorderEndpoint], { exclude: ['secret'] }) + const op = doc.paths?.['/api/reorder']?.post + expect(op?.tags).toEqual(['Reorder']) + expect(op?.security).toEqual([{ PayloadToken: [] }]) + const requestBody = op?.requestBody as RequestBodyObject + const body = (requestBody.content['application/json'] as MediaTypeObject).schema as Schema + expect(body.properties?.collectionSlug).toEqual({ type: 'string', enum: ['posts', 'options'] }) + expect(doc.tags?.find((tag) => tag.name === 'Reorder')?.parent).toBe(NAV_SYSTEM) + const result = await new Validator().validate(structuredClone(doc)) + expect(result.valid).toBe(true) + }) + + it('leaves `/reorder` to `custom.openapi` when the endpoint has it', async () => { + const meta = { responses: { '200': { description: 'mine' } } } + const doc = await build([{ ...reorderEndpoint, custom: { openapi: meta } }]) + expect(doc.paths?.['/api/reorder']?.post).toEqual(meta) + }) + }) + + describe('preferences', () => { + const preferences = coll('payload-preferences', { fields: [], flattenedFields: [] }) + const build = (includeSystem: boolean) => + buildDocument( + baseInput({ + config: { collections: [preferences], blocks: [] } as unknown as SanitizedConfig, + collections: [preferences], + options: resolveOptions({ + info: { title: 'T', version: '1.0.0' }, + filters: { includeSystem, includeAdminAuth: true }, + }), + }), + ) + + it('documents the preference endpoints only with `includeSystem`', async () => { + expect((await build(false)).paths?.['/api/payload-preferences/{id}']).toBeUndefined() + const doc = await build(true) + const item = doc.paths?.['/api/payload-preferences/{id}'] + for (const method of ['get', 'post', 'delete'] as const) { + expect(item?.[method]?.operationId).toMatch(/ByKey$/) + expect(item?.[method]?.security).toEqual([{ PayloadToken: [] }]) + expect(item?.[method]?.parameters).toEqual([expect.objectContaining({ name: 'id', in: 'path' })]) + } + expect(item?.patch?.operationId).toBe('updatePayloadPreferencesById') + expect(doc.paths?.['/api/payload-preferences/count']).toBeUndefined() + expect(doc.paths?.['/api/payload-preferences/validate']).toBeUndefined() + expect(doc.paths?.['/api/payload-preferences/access']).toBeUndefined() + const result = await new Validator().validate(structuredClone(doc)) + expect(result.valid).toBe(true) + }) + }) }) describe('spec/paths/jobs', () => { @@ -632,6 +706,20 @@ describe('spec/paths/system', () => { expect(paths['/api/upload-instructions/{uploadId}']?.delete?.security).toEqual([{ PayloadToken: [] }]) }) + it('adds the reorder endpoint only for orderable collections, with a `{ error }` 400', () => { + expect(buildSystemPaths({ ctx, access: false, uploadSlugs: [], reorderSlugs: [] })).toEqual({}) + const op = buildSystemPaths({ ctx, access: false, uploadSlugs: [], reorderSlugs: ['posts'] })['/api/reorder'] + ?.post + expect(op?.operationId).toBe('reorder') + expect(op?.security).toEqual([{ PayloadToken: [] }]) + const bad = op?.responses?.['400'] as { content: Record } + expect(bad.content['application/json']?.schema).toEqual({ + type: 'object', + properties: { error: { type: 'string' } }, + required: ['error'], + }) + }) + it('adds the root access endpoint only when `access` is on', () => { expect(buildSystemPaths({ ctx, access: true, uploadSlugs: [] })['/api/access']?.get).toBeDefined() }) @@ -761,6 +849,27 @@ describe('spec/paths/collections', () => { expect(paths['/api/posts/file/{filename}']?.get?.responses?.['206']).toBeDefined() }) + it('documents file rename only for upload collections, secured like update', () => { + expect(buildCollectionPaths({ collection: postsColl(), ctx })['/api/posts/{id}/rename']).toBeUndefined() + const security = { update: [{ PayloadAuth: [] }] } + const paths = buildCollectionPaths({ collection: postsColl({ upload: {} as never }), ctx, security }) + const rename = paths['/api/posts/{id}/rename']?.post + const body = rename?.requestBody as { content: Record } + expect(body.content['application/json']?.schema.required).toEqual(['filename']) + expect(rename?.security).toEqual(security.update) + }) + + it('lists the `version` query param on the file endpoint only with versions', () => { + const names = (versions: unknown) => + ( + (buildCollectionPaths({ collection: postsColl({ upload: {} as never, versions } as never), ctx })[ + '/api/posts/file/{filename}' + ]?.parameters ?? []) as { name: string }[] + ).map((p) => p.name) + expect(names(false)).toEqual(['filename']) + expect(names({})).toEqual(['filename', 'version']) + }) + it('drops bulk update/delete when `disableBulkEdit` is set, keeping by-id ops', () => { const paths = buildCollectionPaths({ collection: postsColl({ disableBulkEdit: true }), ctx }) expect(paths['/api/posts']?.patch).toBeUndefined() diff --git a/tests/unit/plugins.spec.ts b/tests/unit/plugins.spec.ts new file mode 100644 index 0000000..3aca11a --- /dev/null +++ b/tests/unit/plugins.spec.ts @@ -0,0 +1,405 @@ +import type { Document, OperationObject, ParameterObject } from '@scalar/openapi-types/3.2' +import { Validator } from '@seriousme/openapi-schema-validator' +import type { Endpoint, SanitizedCollectionConfig, SanitizedConfig } from 'payload' +import { describe, expect, it } from 'vitest' + +import { resolveOptions } from '@/server/options/resolveOptions.js' +import { buildDocument } from '@/server/spec/buildDocument.js' +import { buildTagHierarchy, NAV_PLUGINS } from '@/server/spec/tags.js' +import type { OpenApiPluginOptions, Schema } from '@/shared/types/index.js' + +import { baseInput } from '../helpers/context.js' + +const secured = [{ PayloadToken: [] }] + +const plugin = (slug: string, options?: Record) => + Object.assign(() => ({}), { slug, ...(options ? { options } : {}) }) + +const ep = (method: Endpoint['method'], path: string, extra: Partial = {}): Endpoint => + ({ method, path, handler: () => new Response(), ...extra }) as Endpoint + +const coll = (slug: string, extra: Partial = {}): SanitizedCollectionConfig => + ({ slug, fields: [], ...extra }) as SanitizedCollectionConfig + +const build = ( + config: Record, + collections: SanitizedCollectionConfig[] = [], + options: Partial = {}, +): Promise => + buildDocument( + baseInput({ + config: config as unknown as SanitizedConfig, + collections, + options: resolveOptions({ info: { title: 'T', version: '1.0.0' }, ...options }), + }), + ) + +const op = (doc: Document, path: string, method: 'get' | 'post'): OperationObject | undefined => + doc.paths?.[path]?.[method] as OperationObject | undefined + +const bodySchema = (operation: OperationObject | undefined): Schema => + (operation?.requestBody as { content: Record } | undefined)?.content['application/json'] + ?.schema ?? {} + +const tagNames = (doc: Document) => (doc.tags ?? []).map((tag) => tag.name) + +describe('spec/plugins', () => { + it('documents nothing when the plugin is not installed', async () => { + const doc = await build({ endpoints: [ep('post', '/plugin-seo/generate-title'), ep('post', '/mcp')] }) + expect(doc.paths?.['/api/plugin-seo/generate-title']).toBeUndefined() + expect(doc.paths?.['/api/mcp']).toBeUndefined() + }) + + it('documents only the endpoints that are mounted', async () => { + const doc = await build({ + plugins: [plugin('@payloadcms/plugin-seo')], + endpoints: [ep('post', '/plugin-seo/generate-title')], + }) + expect(op(doc, '/api/plugin-seo/generate-title', 'post')?.operationId).toBe('seoGenerateTitle') + expect(doc.paths?.['/api/plugin-seo/generate-description']).toBeUndefined() + }) + + it('documents the SEO generate endpoints as secured', async () => { + const doc = await build({ + plugins: [plugin('@payloadcms/plugin-seo')], + endpoints: ['title', 'description', 'url', 'image'].map((name) => ep('post', `/plugin-seo/generate-${name}`)), + }) + for (const name of ['title', 'description', 'url', 'image']) { + const operation = op(doc, `/api/plugin-seo/generate-${name}`, 'post') + expect(operation?.tags).toEqual(['SEO']) + expect(operation?.security).toEqual(secured) + } + }) + + it('skips a plugin endpoint that carries `custom.openapi`', async () => { + const doc = await build({ + plugins: [plugin('@payloadcms/plugin-seo')], + endpoints: [ + ep('post', '/plugin-seo/generate-title', { + custom: { openapi: { operationId: 'mine', responses: { '200': { description: 'ok' } } } }, + }), + ], + }) + expect(op(doc, '/api/plugin-seo/generate-title', 'post')?.operationId).toBe('mine') + expect(tagNames(doc)).not.toContain('SEO') + }) + + it('lets an operation in `extensions.paths` replace the generated one', async () => { + const mine = { operationId: 'mine', responses: { '200': { description: 'ok' } } } + const doc = await build( + { + plugins: [plugin('@payloadcms/plugin-mcp')], + endpoints: [ep('post', '/mcp'), ep('get', '/mcp')], + }, + [], + { extensions: [{ paths: { '/api/mcp': { summary: 'MCP', post: mine } } }] }, + ) + expect(op(doc, '/api/mcp', 'post')).toEqual(mine) + expect(op(doc, '/api/mcp', 'get')?.operationId).toBe('mcpStream') + expect(doc.paths?.['/api/mcp']?.summary).toBe('MCP') + }) + + it('documents the Stripe webhook as public and the REST proxy as secured', async () => { + const doc = await build({ + plugins: [ + plugin('@payloadcms/plugin-stripe', { rest: { allowedMethods: ['customers.list', 'customers.list'] } }), + ], + endpoints: [ep('post', '/stripe/webhooks'), ep('post', '/stripe/rest')], + }) + const webhook = op(doc, '/api/stripe/webhooks', 'post') + expect(webhook?.security).toBeUndefined() + expect((webhook?.parameters as ParameterObject[] | undefined)?.[0]?.name).toBe('stripe-signature') + const rest = op(doc, '/api/stripe/rest', 'post') + expect(rest?.security).toEqual(secured) + expect(bodySchema(rest).properties?.stripeMethod).toEqual({ type: 'string', enum: ['customers.list'] }) + }) + + it('documents the MCP POST route and the GET route that answers 405', async () => { + const doc = await build({ + plugins: [plugin('@payloadcms/plugin-mcp')], + endpoints: [ep('post', '/mcp'), ep('get', '/mcp')], + }) + expect(op(doc, '/api/mcp', 'post')?.operationId).toBe('mcp') + expect(Object.keys(op(doc, '/api/mcp', 'post')?.responses ?? {}).toSorted()).toEqual([ + '200', + '202', + '400', + '401', + '404', + '406', + '413', + '415', + '500', + ]) + expect(Object.keys(op(doc, '/api/mcp', 'get')?.responses ?? {})).toEqual(['405']) + }) + + it('skips a plugin whose detection throws and keeps the others', async () => { + const doc = await build({ + storage: 'broken', + plugins: [plugin('@payloadcms/plugin-seo')], + endpoints: [ep('post', '/plugin-seo/generate-title'), ep('post', '/storage-r2-multi-part-upload')], + }) + expect(op(doc, '/api/plugin-seo/generate-title', 'post')).toBeDefined() + expect(doc.paths?.['/api/storage-r2-multi-part-upload']).toBeUndefined() + }) + + it('documents search reindex on the configured search collection only', async () => { + const reindex = ep('post', '/reindex') + const doc = await build( + { + plugins: [ + plugin('@payloadcms/plugin-search', { collections: ['posts', 'hidden'], searchOverrides: { slug: 'find' } }), + ], + }, + [coll('find', { endpoints: [reindex] }), coll('other', { endpoints: [reindex] }), coll('posts')], + ) + const operation = op(doc, '/api/find/reindex', 'post') + expect(operation?.operationId).toBe('reindexFind') + expect(operation?.security).toEqual(secured) + expect(bodySchema(operation).properties?.collections).toMatchObject({ items: { enum: ['posts'] } }) + expect(doc.paths?.['/api/other/reindex']).toBeUndefined() + }) + + it('documents collection mounts only for collections that pass the filters', async () => { + const doc = await build( + { plugins: [plugin('@payloadcms/plugin-search')] }, + [coll('search', { endpoints: [ep('post', '/reindex')] })], + { filters: { exclude: ['search'] } }, + ) + expect(doc.paths?.['/api/search/reindex']).toBeUndefined() + }) + + it('documents the tenant options route on the tenants collection', async () => { + const doc = await build({ plugins: [plugin('@payloadcms/plugin-multi-tenant', { tenantsSlug: 'orgs' })] }, [ + coll('orgs', { endpoints: [ep('get', '/populate-tenant-options')] }), + ]) + expect(op(doc, '/api/orgs/populate-tenant-options', 'get')?.operationId).toBe('populateOrgsOptions') + }) + + it('documents import/export routes on the collections the plugin marks', async () => { + const endpoints = [ep('post', '/download'), ep('post', '/export-preview'), ep('post', '/preview-data')] + const doc = await build({ plugins: [plugin('@payloadcms/plugin-import-export')] }, [ + coll('exports', { + endpoints, + admin: { custom: { 'plugin-import-export': { collectionSlugs: ['posts'] } } } as never, + }), + coll('posts', { endpoints }), + ]) + const download = op(doc, '/api/exports/download', 'post') + expect(download?.security).toEqual(secured) + expect(bodySchema(download).properties?.data).toMatchObject({ + properties: { collectionSlug: { enum: ['posts'] } }, + }) + expect(op(doc, '/api/exports/export-preview', 'post')?.security).toBeUndefined() + expect(op(doc, '/api/exports/preview-data', 'post')?.operationId).toBe('previewExportsData') + expect(doc.paths?.['/api/posts/download']).toBeUndefined() + }) + + it('documents the ecommerce cart and payment routes', async () => { + const doc = await build( + { + plugins: [plugin('@payloadcms/plugin-ecommerce')], + endpoints: [ + ep('post', '/payments/stripe/initiate'), + ep('post', '/payments/stripe/confirm-order'), + ep('post', '/payments/stripe/webhooks'), + ep('post', '/payments/bank-transfer/initiate'), + ], + }, + [ + coll('carts', { + endpoints: ['add-item', 'remove-item', 'update-item', 'clear', 'merge'].map((name) => + ep('post', `/:id/${name}`), + ), + }), + ], + ) + const addItem = op(doc, '/api/carts/{id}/add-item', 'post') + expect(addItem?.operationId).toBe('addItemCarts') + expect(addItem?.security).toBeUndefined() + expect(Object.keys(addItem?.responses ?? {})).toEqual(expect.arrayContaining(['403', '404'])) + expect(op(doc, '/api/carts/{id}/merge', 'post')?.security).toEqual(secured) + expect(op(doc, '/api/payments/stripe/initiate', 'post')?.operationId).toBe('initiatePaymentStripe') + expect(bodySchema(op(doc, '/api/payments/stripe/confirm-order', 'post')).required).toEqual(['paymentIntentID']) + expect(op(doc, '/api/payments/stripe/webhooks', 'post')?.tags).toEqual(['Ecommerce']) + expect(op(doc, '/api/payments/bank-transfer/initiate', 'post')?.operationId).toBe('initiatePaymentBankTransfer') + }) + + it('keeps ecommerce payment operation ids unique', async () => { + const doc = await build({ + plugins: [plugin('@payloadcms/plugin-ecommerce')], + endpoints: [ep('post', '/payments/bank-transfer/initiate'), ep('post', '/payments/bank_transfer/initiate')], + }) + expect(op(doc, '/api/payments/bank-transfer/initiate', 'post')?.operationId).toBe('initiatePaymentBankTransfer') + expect(op(doc, '/api/payments/bank_transfer/initiate', 'post')?.operationId).toBe('initiatePaymentBankTransfer2') + }) + + it('keeps a custom operation on the same path as a plugin operation', async () => { + const doc = await build({ + plugins: [plugin('@payloadcms/plugin-mcp')], + endpoints: [ + ep('post', '/mcp'), + ep('get', '/mcp', { + custom: { openapi: { operationId: 'mine', responses: { '200': { description: 'ok' } } } }, + }), + ], + }) + expect(op(doc, '/api/mcp', 'get')?.operationId).toBe('mine') + expect(op(doc, '/api/mcp', 'post')?.operationId).toBe('mcp') + }) + + it('does not leak a transform mutation into the next build', async () => { + const config = { plugins: [plugin('@payloadcms/plugin-mcp')], endpoints: [ep('post', '/mcp')] } + await build(config, [], { + extensions: [ + { + transform: ({ doc }) => { + const schema = bodySchema(op(doc, '/api/mcp', 'post')) + schema.oneOf?.push({ type: 'null' }) + return doc + }, + }, + ], + }) + const doc = await build(config) + expect(bodySchema(op(doc, '/api/mcp', 'post')).oneOf).toHaveLength(2) + }) + + it('detects the R2 adapter from `storage` and keeps the route suffix', async () => { + const doc = await build({ + storage: [{ name: 'r2' }], + endpoints: [ep('post', '/storage-r2-multi-part-upload-2')], + }) + const operation = op(doc, '/api/storage-r2-multi-part-upload-2', 'post') + expect(operation?.operationId).toBe('r2MultipartUpload2') + expect(operation?.security).toEqual(secured) + const none = await build({ endpoints: [ep('post', '/storage-r2-multi-part-upload')] }) + expect(none.paths?.['/api/storage-r2-multi-part-upload']).toBeUndefined() + }) + + describe('filters and security', () => { + const config = { + plugins: [plugin('@payloadcms/plugin-seo'), plugin('@payloadcms/plugin-search')], + endpoints: [ + ep('post', '/plugin-seo/generate-title'), + ep('get', '/health', { + custom: { openapi: { 'x-payload-plugin': 'my-plugin', responses: { '200': { description: 'ok' } } } }, + }), + ep('get', '/plain', { custom: { openapi: { responses: { '200': { description: 'ok' } } } } }), + ], + } + const collections = [coll('search', { endpoints: [ep('post', '/reindex')] })] + + it('drops plugin operations through `excludeOperations` by `plugin`', async () => { + const doc = await build(config, collections, { + filters: { excludeOperations: [{ plugin: /plugin-seo$/ }, { kind: 'plugin', slug: 'search' }] }, + }) + expect(doc.paths?.['/api/plugin-seo/generate-title']).toBeUndefined() + expect(doc.paths?.['/api/search/reindex']).toBeUndefined() + expect(doc.paths?.['/api/plain']).toBeDefined() + }) + + it('never matches a `plugin` rule against an operation without a plugin', async () => { + const doc = await build(config, collections, { filters: { excludeOperations: [{ plugin: /.*/ }] } }) + expect(doc.paths?.['/api/plain']).toBeDefined() + expect(doc.paths?.['/api/health']).toBeUndefined() + expect(doc.paths?.['/api/search/reindex']).toBeUndefined() + }) + + it('passes `kind` and `plugin` to `security`', async () => { + const seen = new Set() + await build(config, collections, { + security: ({ kind, plugin: name, slug, path }) => { + if (kind !== 'system') seen.add(`${kind}:${name ?? '-'}:${slug ?? '-'}:${path}`) + return undefined + }, + }) + expect([...seen].toSorted()).toEqual([ + 'custom:-:-:/api/plain', + 'custom:my-plugin:-:/api/health', + 'plugin:@payloadcms/plugin-search:search:/api/search/reindex', + 'plugin:@payloadcms/plugin-seo:-:/api/plugin-seo/generate-title', + ]) + }) + + it('strips `x-payload-plugin` from the document', async () => { + const doc = await build(config, collections) + expect(op(doc, '/api/health', 'get')).toEqual({ responses: { '200': { description: 'ok' } } }) + }) + }) + + describe('tags', () => { + const config = { + plugins: [plugin('@payloadcms/plugin-seo'), plugin('@payloadcms/plugin-mcp')], + endpoints: [ep('post', '/plugin-seo/generate-title'), ep('post', '/plugin-seo/generate-url')], + } + + it('adds one tag per documented plugin under the Plugins group', async () => { + const doc = await build(config, [], { nestedTags: true }) + const byName = Object.fromEntries((doc.tags ?? []).map((tag) => [tag.name, tag])) + expect(byName[NAV_PLUGINS]?.kind).toBe('nav') + expect(byName['SEO']?.parent).toBe(NAV_PLUGINS) + expect(byName['MCP']).toBeUndefined() + expect(tagNames(doc).filter((name) => name === 'SEO')).toHaveLength(1) + }) + + it('puts a plugin under the collection tag of the same name', () => { + const names = buildTagHierarchy({ + collections: [{ base: 'Search', hasAuth: false, hasVersions: false }], + globals: [], + systemTags: [], + pluginTags: ['Search', 'SEO'], + t: (key) => key, + nested: true, + }).map((tag) => tag.name) + expect(names).toEqual(['Collections', 'Search', NAV_PLUGINS, 'SEO']) + }) + + it('emits no plugin tag objects in flat mode', async () => { + const doc = await build(config, [], { nestedTags: false }) + expect(tagNames(doc)).not.toContain('SEO') + expect(tagNames(doc)).not.toContain(NAV_PLUGINS) + }) + }) + + it('produces a valid document with every plugin installed', async () => { + const doc = await build( + { + plugins: [ + plugin('@payloadcms/plugin-ecommerce'), + plugin('@payloadcms/plugin-stripe'), + plugin('@payloadcms/plugin-mcp'), + plugin('@payloadcms/plugin-seo'), + plugin('@payloadcms/plugin-search'), + plugin('@payloadcms/plugin-multi-tenant'), + plugin('@payloadcms/plugin-import-export'), + ], + storage: [{ name: 'r2' }], + endpoints: [ + ep('post', '/payments/stripe/initiate'), + ep('post', '/payments/stripe/confirm-order'), + ep('post', '/payments/stripe/webhooks'), + ep('post', '/stripe/webhooks'), + ep('post', '/stripe/rest'), + ep('post', '/mcp'), + ep('get', '/mcp'), + ep('post', '/plugin-seo/generate-image'), + ep('post', '/storage-r2-multi-part-upload'), + ], + }, + [ + coll('carts', { endpoints: [ep('post', '/:id/add-item'), ep('post', '/:id/update-item')] }), + coll('search', { endpoints: [ep('post', '/reindex')] }), + coll('tenants', { endpoints: [ep('get', '/populate-tenant-options')] }), + coll('exports', { + endpoints: [ep('post', '/download'), ep('post', '/preview-data')], + admin: { custom: { 'plugin-import-export': { collectionSlugs: [] } } } as never, + }), + ], + ) + const result = await new Validator().validate(JSON.parse(JSON.stringify(doc))) + expect(result.errors).toBeUndefined() + expect(result.valid).toBe(true) + }) +})