@extravirgin/payload-enchants-translator 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +173 -20
  2. package/dist/client/components/GlossaryControls/index.d.ts +4 -0
  3. package/dist/client/components/GlossaryControls/index.d.ts.map +1 -0
  4. package/dist/client/components/GlossaryControls/index.js +43 -0
  5. package/dist/client/components/GlossaryControls/index.js.map +1 -0
  6. package/dist/client/providers/Translator/TranslatorProvider.d.ts.map +1 -1
  7. package/dist/client/providers/Translator/TranslatorProvider.js +2 -1
  8. package/dist/client/providers/Translator/TranslatorProvider.js.map +1 -1
  9. package/dist/exports/client.d.ts +1 -0
  10. package/dist/exports/client.d.ts.map +1 -1
  11. package/dist/exports/client.js +1 -0
  12. package/dist/exports/client.js.map +1 -1
  13. package/dist/glossary.d.ts +17 -0
  14. package/dist/glossary.d.ts.map +1 -0
  15. package/dist/glossary.js +52 -0
  16. package/dist/glossary.js.map +1 -0
  17. package/dist/glossaryCollection.d.ts +5 -0
  18. package/dist/glossaryCollection.d.ts.map +1 -0
  19. package/dist/glossaryCollection.js +114 -0
  20. package/dist/glossaryCollection.js.map +1 -0
  21. package/dist/i18n-translations.d.ts +84 -0
  22. package/dist/i18n-translations.d.ts.map +1 -1
  23. package/dist/i18n-translations.js +83 -0
  24. package/dist/i18n-translations.js.map +1 -1
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +53 -8
  28. package/dist/index.js.map +1 -1
  29. package/dist/resolvers/deepl.d.ts +12 -0
  30. package/dist/resolvers/deepl.d.ts.map +1 -0
  31. package/dist/resolvers/deepl.js +82 -0
  32. package/dist/resolvers/deepl.js.map +1 -0
  33. package/dist/resolvers/deeplGlossary.d.ts +19 -0
  34. package/dist/resolvers/deeplGlossary.d.ts.map +1 -0
  35. package/dist/resolvers/deeplGlossary.js +117 -0
  36. package/dist/resolvers/deeplGlossary.js.map +1 -0
  37. package/dist/resolvers/types.d.ts +3 -0
  38. package/dist/resolvers/types.d.ts.map +1 -1
  39. package/dist/resolvers/types.js.map +1 -1
  40. package/dist/translate/endpoint.d.ts.map +1 -1
  41. package/dist/translate/endpoint.js +2 -1
  42. package/dist/translate/endpoint.js.map +1 -1
  43. package/dist/translate/findEntityWithConfig.d.ts.map +1 -1
  44. package/dist/translate/findEntityWithConfig.js +4 -3
  45. package/dist/translate/findEntityWithConfig.js.map +1 -1
  46. package/dist/translate/operation.d.ts.map +1 -1
  47. package/dist/translate/operation.js +4 -1
  48. package/dist/translate/operation.js.map +1 -1
  49. package/dist/translate/traverseFields.d.ts.map +1 -1
  50. package/dist/translate/traverseFields.js +13 -0
  51. package/dist/translate/traverseFields.js.map +1 -1
  52. package/dist/translate/updateEntity.d.ts.map +1 -1
  53. package/dist/translate/updateEntity.js +3 -2
  54. package/dist/translate/updateEntity.js.map +1 -1
  55. package/dist/types.d.ts +10 -8
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js.map +1 -1
  58. package/package.json +25 -12
  59. package/LICENSE +0 -21
package/README.md CHANGED
@@ -1,8 +1,18 @@
1
- # Translator plugin for Payload 3.0 (beta)
1
+ # Translator plugin for Payload 3
2
2
 
3
3
  ## Install
4
4
 
5
- `pnpm add @payload-enchants/translator`
5
+ ```sh
6
+ pnpm add @extravirgin/payload-enchants-translator
7
+ ```
8
+
9
+ When using the DeepL resolver, also install its optional peer dependency:
10
+
11
+ ```sh
12
+ pnpm add deepl-node@^1.27.0
13
+ ```
14
+
15
+ Use Payload 3 with localization enabled and at least two content locales. Register the collections and globals listed in the plugin options in your Payload config.
6
16
 
7
17
  ## Video
8
18
 
@@ -10,25 +20,38 @@ https://github.com/r1tsuu/payload-plugin-translator/assets/64744993/d39aeba4-baf
10
20
 
11
21
  ## Features:
12
22
 
13
- 1. A flexible structure with [resolvers](https://github.com/r1tsuu/payload-plugin-translator/tree/6d0c8098467f9b5e757bf9fd8cfe63ff5da68d5b/plugin/src/resolvers) that allows you to apply any kind of transformation to your localizated data.
23
+ 1. A flexible structure with [resolvers](./src/resolvers) that allows you to apply any kind of transformation to your localized data.
14
24
  2. Can be used not only from the admin panel, but within Local API as well. [Example of the hook](#example-of-the-hook-that-uses-local-operation-to-copy-the-doc-data-to-other-locales) that automatically fills the other locales data on create
15
- 3. Out of the box supports 3 resolvers - Copy, Google Translate, OpenAI and your own can be written easily.
25
+ 3. Includes Copy, DeepL, Google Translate, LibreTranslate, and OpenAI resolvers, with support for custom resolvers.
16
26
  4. Works with any nested document structure and 2 Rich Text editor adapters - Lexical and Slate.
17
- 5. You can omit some fields to translate. [Documentation](#omitting-fields)
27
+ 5. You can omit fields from translation. [Documentation](#omitting-fields)
28
+ 6. Optional DeepL glossary: synchronize individual entries on save and synchronize the full list manually.
29
+ 7. English and German admin UI messages, following the current UI language.
30
+
31
+ ## Usage
18
32
 
19
- ## Usage:
33
+ Enable the resolvers your project needs. The glossary is optional; the following configuration does not register a glossary collection or sync endpoint.
20
34
 
21
35
  ```ts
22
- import { buildConfig } from 'payload/config';
36
+ import { buildConfig } from 'payload';
37
+ import { en } from '@payloadcms/translations/languages/en';
38
+ import { de } from '@payloadcms/translations/languages/de';
23
39
  import {
24
40
  translator,
25
41
  copyResolver,
26
42
  googleResolver,
27
43
  openAIResolver,
28
44
  libreResolver,
29
- } from '@payload-enchants/translator';
45
+ } from '@extravirgin/payload-enchants-translator';
30
46
 
31
47
  export default buildConfig({
48
+ localization: {
49
+ defaultLocale: 'en',
50
+ locales: ['en', 'de'],
51
+ },
52
+ i18n: {
53
+ supportedLanguages: { en, de },
54
+ },
32
55
  plugins: [
33
56
  translator({
34
57
  // collections with the enabled translator in the admin UI
@@ -55,6 +78,131 @@ export default buildConfig({
55
78
 
56
79
  ## Resolvers
57
80
 
81
+ ### DeepL without a glossary
82
+
83
+ For translation only, omit `glossary` from both `translator()` and `deeplResolver()`, and omit `glossaryId`. Only `DEEPL_API_KEY` is needed; `DEEPL_GLOSSARY_ID` is not required.
84
+
85
+ ```ts
86
+ import { deeplResolver, translator } from '@extravirgin/payload-enchants-translator';
87
+
88
+ export const translatorPlugin = translator({
89
+ collections: ['pages', 'news'],
90
+ globals: ['global-settings'],
91
+ resolvers: [
92
+ deeplResolver({
93
+ apiKey: process.env.DEEPL_API_KEY!,
94
+ targetLocaleMap: { en: 'en-GB' },
95
+ }),
96
+ ],
97
+ });
98
+ ```
99
+
100
+ Add `translatorPlugin` to your Payload `plugins` array. Normal translation works without registering glossary fields, collections, controls, or sync endpoints.
101
+
102
+ The DeepL resolver accepts:
103
+
104
+ ```ts
105
+ export type DeepLResolverConfig = {
106
+ apiKey: string;
107
+ chunkLength?: number; // Concurrent text batch size; default: 100
108
+ glossary?: GlossaryConfig;
109
+ glossaryId?: string; // Existing DeepL multilingual glossary ID
110
+ sourceLocaleMap?: Record<string, string>;
111
+ targetLocaleMap?: Record<string, string>;
112
+ };
113
+ ```
114
+
115
+ Locale maps convert CMS locale codes to provider codes. For example, `en: 'en-GB'` selects British English for text translation; glossary language pairs use the base language `en`.
116
+
117
+ ### DeepL with an optional glossary
118
+
119
+ Configure `DEEPL_API_KEY` and `DEEPL_GLOSSARY_ID` on the server. The glossary ID must identify an existing DeepL multilingual glossary accessible with that API key. The plugin does not create one automatically.
120
+
121
+ Pass the same glossary configuration to the plugin and resolver:
122
+
123
+ ```ts
124
+ import type { Access } from 'payload';
125
+ import type { GlossaryConfig } from '@extravirgin/payload-enchants-translator';
126
+ import { deeplResolver, translator } from '@extravirgin/payload-enchants-translator';
127
+
128
+ // Replace this example with your project's admin/editor access policy.
129
+ const authenticated: Access = ({ req }) => Boolean(req.user);
130
+
131
+ const glossary: GlossaryConfig = {
132
+ slug: 'translation-glossary',
133
+ sourceLocale: 'de',
134
+ targetLocales: ['fr', 'it', 'en'],
135
+ access: { read: authenticated, update: authenticated },
136
+ localeLabels: {
137
+ de: { en: 'German (source)', de: 'Deutsch (Quelle)' },
138
+ fr: { en: 'French', de: 'Französisch' },
139
+ it: { en: 'Italian', de: 'Italienisch' },
140
+ en: { en: 'English', de: 'Englisch' },
141
+ },
142
+ };
143
+
144
+ export const translatorPlugin = translator({
145
+ collections: ['pages', 'news'],
146
+ globals: ['global-settings'],
147
+ glossary,
148
+ resolvers: [
149
+ deeplResolver({
150
+ apiKey: process.env.DEEPL_API_KEY!,
151
+ glossary,
152
+ glossaryId: process.env.DEEPL_GLOSSARY_ID!,
153
+ targetLocaleMap: { en: 'en-GB' },
154
+ }),
155
+ ],
156
+ });
157
+ ```
158
+
159
+ Configure the corresponding content locales in Payload. After adding the plugin or changing its schema, run your project's Payload migration, type-generation, and import-map workflow.
160
+
161
+ #### Storage and validation
162
+
163
+ - Entries use a collection named `<slug>-records`, or `translation-glossary-records` with the default slug. There is no glossary global or sync-state collection.
164
+ - The source language is required. In the example, German is required and French, Italian, and English are optional.
165
+ - Terms are trimmed, must be single-line without tabs, and may contain at most 1,024 UTF-8 bytes.
166
+ - Source terms must be unique after trimming, Unicode normalization, and case normalization.
167
+ - `access.read` controls reading. `access.update` controls creating, editing, and deleting entries. Synchronization requires both callbacks to return `true`.
168
+
169
+ #### Saving an entry
170
+
171
+ Creating or editing an entry sends only that item's populated translations to DeepL. Blank target languages are skipped. Other Payload glossary entries are neither read nor uploaded.
172
+
173
+ Every save resends the item's populated translations, including unchanged ones. A German-only entry sends no dictionary updates. Saves wait for DeepL; synchronization errors are reported as save failures. There is no background worker, job queue, or automatic synchronization retry.
174
+
175
+ #### Manual full synchronization
176
+
177
+ The **Sync all to DeepL** button appears at the top-right of the glossary list. Its tooltip explains when full synchronization is needed.
178
+
179
+ | Action | On save | After Sync all |
180
+ | ---------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------- |
181
+ | Add an item or edit a target translation | Merges that item's populated translations | Replaces the full configured dictionaries |
182
+ | Rename the source term | Adds the new term; the old term remains in DeepL | Removes the old term |
183
+ | Clear a target translation | Skips the empty value; the previous translation remains in DeepL | Removes the previous translation |
184
+ | Delete an item | No DeepL request | Removes the deleted item from DeepL |
185
+
186
+ Full synchronization reads all saved entries in pages of 1,000, regardless of the list's current filters or page. It replaces each configured source/target dictionary and removes configured dictionaries that have no remaining entries. Unrelated language pairs in the same DeepL glossary are preserved. The uploaded glossary data is limited to 10 MiB.
187
+
188
+ The button is disabled while syncing, then displays a success or error notification. Avoid editing entries while full synchronization runs. DeepL updates are not atomic across languages: a failed operation may already have updated some dictionaries. Retry the item save or use Sync all to reconcile DeepL with the saved Payload entries.
189
+
190
+ The manual action uses `POST <basePath><apiRoute>/translator/glossary/sync`, where the default Payload API route is `/api`.
191
+
192
+ #### Using the glossary during translation
193
+
194
+ The resolver attaches the configured glossary when translating from its source locale to a configured target locale and at least one saved entry has a translation for that target. If there are no populated terms for that target, translation proceeds without a glossary. Other language directions also translate without it.
195
+
196
+ Translating content does not synchronize the glossary. Run Sync all once to upload existing entries, and again after source renames, cleared translations, or deletions. In the admin editor, translated content still needs to be saved; both “Translate all” and “Translate only empty fields” are available.
197
+
198
+ To omit the glossary feature entirely, remove `glossary` from both configurations and remove `glossaryId`. Setting `disabled: true` alone does not remove an explicitly configured glossary collection.
199
+
200
+ ### Admin UI language
201
+
202
+ The plugin includes English and German labels, buttons, tooltips, validation messages, and user-facing errors. They follow the current admin UI language, independently of the document's content locale. Enable `en` and `de` under Payload's `i18n.supportedLanguages`, as shown above.
203
+
204
+ Project translations under `i18n.translations[language]['plugin-translator']` override the plugin defaults. Detailed provider failures are logged on the server; glossary controls display localized error messages.
205
+
58
206
  ### OpenAI
59
207
 
60
208
  #### Config:
@@ -62,9 +210,10 @@ export default buildConfig({
62
210
  ```ts
63
211
  export type OpenAIResolverConfig = {
64
212
  apiKey: string; // API key
213
+ baseUrl?: string; // Optional API base URL
65
214
  chunkLength?: number; // How many texts to include into 1 request, default: 100
66
215
  model?: string; // model, default: 'gpt-3.5-turbo'
67
- promt?: OpenAIPrompt; // custom prompt
216
+ prompt?: OpenAIPrompt; // custom prompt
68
217
  };
69
218
  ```
70
219
 
@@ -78,7 +227,7 @@ export type OpenAIPrompt = (args: {
78
227
  }) => string;
79
228
 
80
229
  // Default
81
- const defaultPromt: OpenAIPrompt = ({ localeFrom, localeTo, texts }) => {
230
+ const defaultPrompt: OpenAIPrompt = ({ localeFrom, localeTo, texts }) => {
82
231
  return `Translate me the following array: ${JSON.stringify(texts)} in locale=${localeFrom} to locale ${localeTo}, respond me with the same array structure`;
83
232
  };
84
233
  ```
@@ -90,14 +239,17 @@ const defaultPromt: OpenAIPrompt = ({ localeFrom, localeTo, texts }) => {
90
239
  ```ts
91
240
  export type GoogleResolverConfig = {
92
241
  apiKey: string; // API key
93
- chunkLength?: number; / /How many texts to include into 1 request, default: 100
242
+ chunkLength?: number; // How many texts to include into 1 request, default: 100
94
243
  };
95
244
  ```
96
245
 
97
246
  ### Writing your own
98
247
 
99
248
  ```ts
100
- import type { TranslateResolver } from '@payload-enchants/translator/resolvers/types';
249
+ import { buildConfig } from 'payload';
250
+ import { en } from '@payloadcms/translations/languages/en';
251
+ import { translator } from '@extravirgin/payload-enchants-translator';
252
+ import type { TranslateResolver } from '@extravirgin/payload-enchants-translator/resolvers/types';
101
253
 
102
254
  const myResolver: TranslateResolver = {
103
255
  key: 'my',
@@ -115,7 +267,7 @@ const myResolver: TranslateResolver = {
115
267
 
116
268
  export default buildConfig({
117
269
  plugins: [
118
- payloadPluginTranslator({
270
+ translator({
119
271
  collections: ['posts', 'small-posts'],
120
272
  globals: [],
121
273
  resolvers: [myResolver],
@@ -127,7 +279,7 @@ export default buildConfig({
127
279
  translations: {
128
280
  en: {
129
281
  'plugin-translator': {
130
- resolver_my_buttonLabel: 'Google Translate',
282
+ resolver_my_buttonLabel: 'Custom translation',
131
283
  resolver_my_errorMessage: 'An error occurred when trying to translate the data',
132
284
  resolver_my_modalTitle: 'Choose the locale to translate from',
133
285
  resolver_my_submitButtonLabelEmpty: 'Translate only empty fields',
@@ -143,7 +295,7 @@ export default buildConfig({
143
295
  ## Using the Local API
144
296
 
145
297
  ```ts
146
- import { translateOperation } from '@payload-enchants/translator';
298
+ import { translateOperation } from '@extravirgin/payload-enchants-translator';
147
299
 
148
300
  const translateResult = await translateOperation({
149
301
  collectionSlug: 'posts', // or globalSlug if globals,
@@ -151,20 +303,21 @@ const translateResult = await translateOperation({
151
303
  id: postDefaultLocale.id, // pass the doc id if it's a collection
152
304
  locale: 'de', // locale to translate to
153
305
  localeFrom: 'en', // locale to translate from
154
- payload, // payload instance, you can get it with getPayload or req.payload in hooks
155
- req, // PayloadRequest, can be used instead of payload, more appreciable than payload if you have it
306
+ req, // Prefer the current PayloadRequest to preserve user, UI language, and transaction context
156
307
  overrideAccess: false, //
157
308
  resolver: 'copy', // pass resolver key
158
309
  update: true, // optional, should update immediately or just return the translated result, default false
159
- data: {}, // Optional, if you want to translate from your data passed here instead of the current doc, should be in "localeFrom" locale, for example { title: "Hello" }
310
+ // data: destinationFormData, // Optional destination data; the source is read from localeFrom
160
311
  });
161
312
  ```
162
313
 
314
+ For calls without an existing request, `payload` can be passed instead of `req`. When using glossary access rules based on the current user, pass an authenticated `req`.
315
+
163
316
  ### Example of the hook that uses local operation to copy the doc data to other locales
164
317
 
165
318
  ```ts
166
- import type { CollectionAfterChangeHook } from 'payload/types';
167
- import { translateOperation } from '@payload-enchants/translator';
319
+ import type { CollectionAfterChangeHook } from 'payload';
320
+ import { translateOperation } from '@extravirgin/payload-enchants-translator';
168
321
 
169
322
  export const copyOtherLocales: CollectionAfterChangeHook = async ({
170
323
  collection,
@@ -0,0 +1,4 @@
1
+ export declare const GlossaryControls: ({ basePath }: {
2
+ basePath: string;
3
+ }) => import("react").JSX.Element;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/client/components/GlossaryControls/index.tsx"],"names":[],"mappings":"AAOA,eAAO,MAAM,gBAAgB,iBAAkB;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,gCA0ClE,CAAC"}
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Button, toast, useConfig, useTranslation } from '@payloadcms/ui';
4
+ import { useState } from 'react';
5
+ import { translatorT } from '../../../i18n-translations';
6
+ export const GlossaryControls = ({ basePath })=>{
7
+ const { config } = useConfig();
8
+ const { i18n, t } = useTranslation();
9
+ const [syncing, setSyncing] = useState(false);
10
+ const sync = async ()=>{
11
+ setSyncing(true);
12
+ try {
13
+ const response = await fetch(`${config.serverURL}${basePath}${config.routes.api}/translator/glossary/sync`, {
14
+ credentials: 'include',
15
+ headers: {
16
+ 'Accept-Language': i18n.language
17
+ },
18
+ method: 'POST'
19
+ });
20
+ const result = await response.json();
21
+ if (!response.ok) {
22
+ toast.error(result.error || translatorT(t, 'glossary_syncError'));
23
+ return;
24
+ }
25
+ toast.success(translatorT(t, 'glossary_syncSuccess'));
26
+ } catch {
27
+ toast.error(translatorT(t, 'glossary_syncError'));
28
+ } finally{
29
+ setSyncing(false);
30
+ }
31
+ };
32
+ return /*#__PURE__*/ _jsx(Button, {
33
+ disabled: syncing,
34
+ margin: false,
35
+ onClick: sync,
36
+ size: "small",
37
+ tooltip: translatorT(t, 'glossary_syncHint'),
38
+ type: "button",
39
+ children: translatorT(t, syncing ? 'glossary_syncing' : 'glossary_syncAll')
40
+ });
41
+ };
42
+
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/client/components/GlossaryControls/index.tsx"],"sourcesContent":["'use client';\n\nimport { Button, toast, useConfig, useTranslation } from '@payloadcms/ui';\nimport { useState } from 'react';\n\nimport { translatorT } from '../../../i18n-translations';\n\nexport const GlossaryControls = ({ basePath }: { basePath: string }) => {\n const { config } = useConfig();\n\n const { i18n, t } = useTranslation();\n\n const [syncing, setSyncing] = useState(false);\n\n const sync = async () => {\n setSyncing(true);\n try {\n const response = await fetch(\n `${config.serverURL}${basePath}${config.routes.api}/translator/glossary/sync`,\n { credentials: 'include', headers: { 'Accept-Language': i18n.language }, method: 'POST' },\n );\n\n const result = await response.json();\n\n if (!response.ok) {\n toast.error(result.error || translatorT(t, 'glossary_syncError'));\n\n return;\n }\n toast.success(translatorT(t, 'glossary_syncSuccess'));\n } catch {\n toast.error(translatorT(t, 'glossary_syncError'));\n } finally {\n setSyncing(false);\n }\n };\n\n return (\n <Button\n disabled={syncing}\n margin={false}\n onClick={sync}\n size='small'\n tooltip={translatorT(t, 'glossary_syncHint')}\n type='button'\n >\n {translatorT(t, syncing ? 'glossary_syncing' : 'glossary_syncAll')}\n </Button>\n );\n};\n"],"names":["Button","toast","useConfig","useTranslation","useState","translatorT","GlossaryControls","basePath","config","i18n","t","syncing","setSyncing","sync","response","fetch","serverURL","routes","api","credentials","headers","language","method","result","json","ok","error","success","disabled","margin","onClick","size","tooltip","type"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAiB;AAC1E,SAASC,QAAQ,QAAQ,QAAQ;AAEjC,SAASC,WAAW,QAAQ,6BAA6B;AAEzD,OAAO,MAAMC,mBAAmB,CAAC,EAAEC,QAAQ,EAAwB;IACjE,MAAM,EAAEC,MAAM,EAAE,GAAGN;IAEnB,MAAM,EAAEO,IAAI,EAAEC,CAAC,EAAE,GAAGP;IAEpB,MAAM,CAACQ,SAASC,WAAW,GAAGR,SAAS;IAEvC,MAAMS,OAAO;QACXD,WAAW;QACX,IAAI;YACF,MAAME,WAAW,MAAMC,MACrB,GAAGP,OAAOQ,SAAS,GAAGT,WAAWC,OAAOS,MAAM,CAACC,GAAG,CAAC,yBAAyB,CAAC,EAC7E;gBAAEC,aAAa;gBAAWC,SAAS;oBAAE,mBAAmBX,KAAKY,QAAQ;gBAAC;gBAAGC,QAAQ;YAAO;YAG1F,MAAMC,SAAS,MAAMT,SAASU,IAAI;YAElC,IAAI,CAACV,SAASW,EAAE,EAAE;gBAChBxB,MAAMyB,KAAK,CAACH,OAAOG,KAAK,IAAIrB,YAAYK,GAAG;gBAE3C;YACF;YACAT,MAAM0B,OAAO,CAACtB,YAAYK,GAAG;QAC/B,EAAE,OAAM;YACNT,MAAMyB,KAAK,CAACrB,YAAYK,GAAG;QAC7B,SAAU;YACRE,WAAW;QACb;IACF;IAEA,qBACE,KAACZ;QACC4B,UAAUjB;QACVkB,QAAQ;QACRC,SAASjB;QACTkB,MAAK;QACLC,SAAS3B,YAAYK,GAAG;QACxBuB,MAAK;kBAEJ5B,YAAYK,GAAGC,UAAU,qBAAqB;;AAGrD,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"TranslatorProvider.d.ts","sourceRoot":"","sources":["../../../../src/client/providers/Translator/TranslatorProvider.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,SAAS,EAAgC,MAAM,OAAO,CAAC;AASrE,eAAO,MAAM,kBAAkB,iBAAkB;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,gCAmJvE,CAAC"}
1
+ {"version":3,"file":"TranslatorProvider.d.ts","sourceRoot":"","sources":["../../../../src/client/providers/Translator/TranslatorProvider.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,SAAS,EAAgC,MAAM,OAAO,CAAC;AAUrE,eAAO,MAAM,kBAAkB,iBAAkB;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,gCAmJvE,CAAC"}
@@ -3,6 +3,7 @@ import { toast, useAllFormFields, useConfig, useDocumentInfo, useForm, useLocale
3
3
  import { reduceFieldsToValues } from 'payload/shared';
4
4
  import { useEffect, useMemo, useState } from 'react';
5
5
  import { createClient } from '../../api';
6
+ import { translatorT } from '../../../i18n-translations';
6
7
  import { TranslatorContext } from './context';
7
8
  const modalSlug = 'translator-modal';
8
9
  export const TranslatorProvider = ({ children })=>{
@@ -35,7 +36,7 @@ export const TranslatorProvider = ({ children })=>{
35
36
  custom,
36
37
  resolver
37
38
  ]);
38
- if (!localization) throw new Error('Localization config is not provided and PluginTranslator is used');
39
+ if (!localization) throw new Error(translatorT(t, 'error_missingLocalization'));
39
40
  const localesOptions = localization.locales.filter((each)=>each.code !== locale.code);
40
41
  const [localeToTranslateFrom, setLocaleToTranslateFrom] = useState('');
41
42
  useEffect(()=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/client/providers/Translator/TranslatorProvider.tsx"],"sourcesContent":["import {\n toast,\n useAllFormFields,\n useConfig,\n useDocumentInfo,\n useForm,\n useLocale,\n useModal,\n useServerFunctions,\n useTranslation,\n} from '@payloadcms/ui';\nimport { reduceFieldsToValues } from 'payload/shared';\nimport { type ReactNode, useEffect, useMemo, useState } from 'react';\n\nimport type { TranslateResolver } from '../../../resolvers/types';\nimport type { TranslateArgs } from '../../../translate/types';\nimport { createClient } from '../../api';\nimport { TranslatorContext } from './context';\n\nconst modalSlug = 'translator-modal';\n\nexport const TranslatorProvider = ({ children }: { children: ReactNode }) => {\n const [resolver, setResolver] = useState<null | string>(null);\n\n const [data, dispatch] = useAllFormFields();\n\n const { getFormState } = useServerFunctions();\n\n const { collectionSlug, getDocPreferences, globalSlug, id } = useDocumentInfo();\n\n const { setModified } = useForm();\n\n const modal = useModal();\n\n const { t } = useTranslation();\n\n const resolverT = (\n key:\n | 'buttonLabel'\n | 'errorMessage'\n | 'modalTitle'\n | 'submitButtonLabelEmpty'\n | 'submitButtonLabelFull'\n | 'successMessage',\n ) => {\n if (!resolver) return '';\n\n return t(`plugin-translator:resolver_${resolver}_${key}` as Parameters<typeof t>[0]);\n };\n\n const locale = useLocale();\n\n const {\n config: {\n admin: { custom },\n localization,\n routes: { api },\n serverURL,\n },\n } = useConfig();\n\n const basePath = (custom?.translator?.basePath as string | undefined) ?? '';\n const apiClient = createClient({ api, basePath, serverURL });\n\n const resolverConfig = useMemo(() => {\n if (!resolver) return null;\n\n const resolvers = (custom?.translator?.resolvers as TranslateResolver[]) || undefined;\n\n if (!resolvers) return null;\n\n const resolverConfig = resolvers.find((each) => each.key === resolver);\n\n return resolverConfig ?? null;\n }, [custom, resolver]);\n\n if (!localization)\n throw new Error('Localization config is not provided and PluginTranslator is used');\n\n const localesOptions = localization.locales.filter((each) => each.code !== locale.code);\n\n const [localeToTranslateFrom, setLocaleToTranslateFrom] = useState<string>('');\n\n useEffect(()=>{\n const localeToTranslateFromExists = localesOptions.find((each)=>localeToTranslateFrom === each.code);\n if (localeToTranslateFrom && localeToTranslateFromExists) return;\n const defaultFromOptions = localesOptions.find((each)=>localization.defaultLocale === each.code);\n if (defaultFromOptions) {\n setLocaleToTranslateFrom(defaultFromOptions.code);\n return;\n }\n setLocaleToTranslateFrom(localesOptions[0].code);\n}, [\n locale,\n localesOptions,\n localization.defaultLocale\n]);\n\n const closeTranslator = () => modal.closeModal(modalSlug);\n\n const submit = async ({ emptyOnly }: { emptyOnly: boolean }) => {\n if (!resolver) return;\n\n const args: TranslateArgs = {\n collectionSlug,\n data: reduceFieldsToValues(data, true),\n emptyOnly,\n globalSlug,\n id: id === null ? undefined : id,\n locale: locale.code,\n localeFrom: localeToTranslateFrom,\n resolver,\n };\n\n const result = await apiClient.translate(args);\n\n if (!result.success) {\n toast.error(resolverT('errorMessage'));\n\n return;\n }\n\n const { state } = await getFormState({\n collectionSlug,\n data: result.translatedData,\n docPermissions: {\n fields: true,\n update: true,\n },\n docPreferences: await getDocPreferences(),\n globalSlug,\n locale: locale.code,\n operation: 'update',\n renderAllFields: true,\n schemaPath: collectionSlug || globalSlug || '',\n });\n\n if (state) {\n dispatch({\n state,\n type: 'REPLACE_STATE',\n });\n setModified(true);\n toast.success(resolverT('successMessage'));\n }\n closeTranslator();\n };\n\n return (\n <TranslatorContext.Provider\n value={{\n closeTranslator,\n localeToTranslateFrom,\n localesOptions,\n modalSlug,\n openTranslator: ({ resolverKey }) => {\n setResolver(resolverKey);\n modal.openModal(modalSlug);\n },\n resolver: resolverConfig,\n resolverT,\n setLocaleToTranslateFrom,\n submit,\n }}\n >\n {children}\n </TranslatorContext.Provider>\n );\n};\n"],"names":["toast","useAllFormFields","useConfig","useDocumentInfo","useForm","useLocale","useModal","useServerFunctions","useTranslation","reduceFieldsToValues","useEffect","useMemo","useState","createClient","TranslatorContext","modalSlug","TranslatorProvider","children","resolver","setResolver","data","dispatch","getFormState","collectionSlug","getDocPreferences","globalSlug","id","setModified","modal","t","resolverT","key","locale","config","admin","custom","localization","routes","api","serverURL","basePath","translator","apiClient","resolverConfig","resolvers","undefined","find","each","Error","localesOptions","locales","filter","code","localeToTranslateFrom","setLocaleToTranslateFrom","localeToTranslateFromExists","defaultFromOptions","defaultLocale","closeTranslator","closeModal","submit","emptyOnly","args","localeFrom","result","translate","success","error","state","translatedData","docPermissions","fields","update","docPreferences","operation","renderAllFields","schemaPath","type","Provider","value","openTranslator","resolverKey","openModal"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/client/providers/Translator/TranslatorProvider.tsx"],"sourcesContent":["import {\n toast,\n useAllFormFields,\n useConfig,\n useDocumentInfo,\n useForm,\n useLocale,\n useModal,\n useServerFunctions,\n useTranslation,\n} from '@payloadcms/ui';\nimport { reduceFieldsToValues } from 'payload/shared';\nimport { type ReactNode, useEffect, useMemo, useState } from 'react';\n\nimport type { TranslateResolver } from '../../../resolvers/types';\nimport type { TranslateArgs } from '../../../translate/types';\nimport { createClient } from '../../api';\nimport { translatorT } from '../../../i18n-translations';\nimport { TranslatorContext } from './context';\n\nconst modalSlug = 'translator-modal';\n\nexport const TranslatorProvider = ({ children }: { children: ReactNode }) => {\n const [resolver, setResolver] = useState<null | string>(null);\n\n const [data, dispatch] = useAllFormFields();\n\n const { getFormState } = useServerFunctions();\n\n const { collectionSlug, getDocPreferences, globalSlug, id } = useDocumentInfo();\n\n const { setModified } = useForm();\n\n const modal = useModal();\n\n const { t } = useTranslation();\n\n const resolverT = (\n key:\n | 'buttonLabel'\n | 'errorMessage'\n | 'modalTitle'\n | 'submitButtonLabelEmpty'\n | 'submitButtonLabelFull'\n | 'successMessage',\n ) => {\n if (!resolver) return '';\n\n return t(`plugin-translator:resolver_${resolver}_${key}` as Parameters<typeof t>[0]);\n };\n\n const locale = useLocale();\n\n const {\n config: {\n admin: { custom },\n localization,\n routes: { api },\n serverURL,\n },\n } = useConfig();\n\n const basePath = (custom?.translator?.basePath as string | undefined) ?? '';\n const apiClient = createClient({ api, basePath, serverURL });\n\n const resolverConfig = useMemo(() => {\n if (!resolver) return null;\n\n const resolvers = (custom?.translator?.resolvers as TranslateResolver[]) || undefined;\n\n if (!resolvers) return null;\n\n const resolverConfig = resolvers.find((each) => each.key === resolver);\n\n return resolverConfig ?? null;\n }, [custom, resolver]);\n\n if (!localization)\n throw new Error(translatorT(t, 'error_missingLocalization'));\n\n const localesOptions = localization.locales.filter((each) => each.code !== locale.code);\n\n const [localeToTranslateFrom, setLocaleToTranslateFrom] = useState<string>('');\n\n useEffect(()=>{\n const localeToTranslateFromExists = localesOptions.find((each)=>localeToTranslateFrom === each.code);\n if (localeToTranslateFrom && localeToTranslateFromExists) return;\n const defaultFromOptions = localesOptions.find((each)=>localization.defaultLocale === each.code);\n if (defaultFromOptions) {\n setLocaleToTranslateFrom(defaultFromOptions.code);\n return;\n }\n setLocaleToTranslateFrom(localesOptions[0].code);\n}, [\n locale,\n localesOptions,\n localization.defaultLocale\n]);\n\n const closeTranslator = () => modal.closeModal(modalSlug);\n\n const submit = async ({ emptyOnly }: { emptyOnly: boolean }) => {\n if (!resolver) return;\n\n const args: TranslateArgs = {\n collectionSlug,\n data: reduceFieldsToValues(data, true),\n emptyOnly,\n globalSlug,\n id: id === null ? undefined : id,\n locale: locale.code,\n localeFrom: localeToTranslateFrom,\n resolver,\n };\n\n const result = await apiClient.translate(args);\n\n if (!result.success) {\n toast.error(resolverT('errorMessage'));\n\n return;\n }\n\n const { state } = await getFormState({\n collectionSlug,\n data: result.translatedData,\n docPermissions: {\n fields: true,\n update: true,\n },\n docPreferences: await getDocPreferences(),\n globalSlug,\n locale: locale.code,\n operation: 'update',\n renderAllFields: true,\n schemaPath: collectionSlug || globalSlug || '',\n });\n\n if (state) {\n dispatch({\n state,\n type: 'REPLACE_STATE',\n });\n setModified(true);\n toast.success(resolverT('successMessage'));\n }\n closeTranslator();\n };\n\n return (\n <TranslatorContext.Provider\n value={{\n closeTranslator,\n localeToTranslateFrom,\n localesOptions,\n modalSlug,\n openTranslator: ({ resolverKey }) => {\n setResolver(resolverKey);\n modal.openModal(modalSlug);\n },\n resolver: resolverConfig,\n resolverT,\n setLocaleToTranslateFrom,\n submit,\n }}\n >\n {children}\n </TranslatorContext.Provider>\n );\n};\n"],"names":["toast","useAllFormFields","useConfig","useDocumentInfo","useForm","useLocale","useModal","useServerFunctions","useTranslation","reduceFieldsToValues","useEffect","useMemo","useState","createClient","translatorT","TranslatorContext","modalSlug","TranslatorProvider","children","resolver","setResolver","data","dispatch","getFormState","collectionSlug","getDocPreferences","globalSlug","id","setModified","modal","t","resolverT","key","locale","config","admin","custom","localization","routes","api","serverURL","basePath","translator","apiClient","resolverConfig","resolvers","undefined","find","each","Error","localesOptions","locales","filter","code","localeToTranslateFrom","setLocaleToTranslateFrom","localeToTranslateFromExists","defaultFromOptions","defaultLocale","closeTranslator","closeModal","submit","emptyOnly","args","localeFrom","result","translate","success","error","state","translatedData","docPermissions","fields","update","docPreferences","operation","renderAllFields","schemaPath","type","Provider","value","openTranslator","resolverKey","openModal"],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  export { CustomButtonWithTranslator } from '../client/components/CustomButton/CustomButtonWithTranslator';
2
+ export { GlossaryControls } from '../client/components/GlossaryControls';
2
3
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8DAA8D,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8DAA8D,CAAC;AAE1G,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export { CustomButtonWithTranslator } from '../client/components/CustomButton/CustomButtonWithTranslator';
2
+ export { GlossaryControls } from '../client/components/GlossaryControls';
2
3
 
3
4
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { CustomButtonWithTranslator } from '../client/components/CustomButton/CustomButtonWithTranslator';\n"],"names":["CustomButtonWithTranslator"],"mappings":"AAAA,SAASA,0BAA0B,QAAQ,+DAA+D"}
1
+ {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { CustomButtonWithTranslator } from '../client/components/CustomButton/CustomButtonWithTranslator';\n\nexport { GlossaryControls } from '../client/components/GlossaryControls';\n"],"names":["CustomButtonWithTranslator","GlossaryControls"],"mappings":"AAAA,SAASA,0BAA0B,QAAQ,+DAA+D;AAE1G,SAASC,gBAAgB,QAAQ,wCAAwC"}
@@ -0,0 +1,17 @@
1
+ import type { TFunction } from '@payloadcms/translations';
2
+ import type { Access, CollectionConfig } from 'payload';
3
+ export type GlossaryConfig = {
4
+ access: {
5
+ read: Access;
6
+ update: Access;
7
+ };
8
+ label?: NonNullable<CollectionConfig['labels']>['plural'];
9
+ localeLabels?: Record<string, Record<string, string> | string>;
10
+ slug?: string;
11
+ sourceLocale: string;
12
+ targetLocales: string[];
13
+ };
14
+ export declare const getGlossarySlug: (config: GlossaryConfig) => string;
15
+ export declare const validateGlossaryEntries: (value: unknown, config: GlossaryConfig, t?: TFunction) => string | true;
16
+ export declare const validateGlossaryConfig: (config: GlossaryConfig) => void;
17
+ //# sourceMappingURL=glossary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glossary.d.ts","sourceRoot":"","sources":["../src/glossary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIxD,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,CAAC;AAEF,eAAO,MAAM,eAAe,WAAY,cAAc,WAA0C,CAAC;AAEjG,eAAO,MAAM,uBAAuB,UAC3B,OAAO,UACN,cAAc,MAClB,SAAS,KACZ,MAAM,GAAG,IAkCX,CAAC;AAEF,eAAO,MAAM,sBAAsB,WAAY,cAAc,KAAG,IAyB/D,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { translatorT } from './i18n-translations';
2
+ export const getGlossarySlug = (config)=>config.slug ?? 'translation-glossary';
3
+ export const validateGlossaryEntries = (value, config, t)=>{
4
+ if (!Array.isArray(value) || !value.length) return translatorT(t, 'glossary_empty');
5
+ const locales = [
6
+ config.sourceLocale,
7
+ ...config.targetLocales
8
+ ];
9
+ const sources = new Set();
10
+ for (const entry of value){
11
+ for (const locale of locales){
12
+ const term = entry?.[locale];
13
+ if (locale !== config.sourceLocale && (term == null || typeof term === 'string' && !term.trim())) {
14
+ continue;
15
+ }
16
+ if (typeof term !== 'string' || !term.trim() || /[\t\r\n]/.test(term)) {
17
+ return translatorT(t, 'glossary_invalidTerm', {
18
+ locale: config.sourceLocale.toUpperCase()
19
+ });
20
+ }
21
+ if (Buffer.byteLength(term.trim(), 'utf8') > 1024) {
22
+ return translatorT(t, 'glossary_termTooLong');
23
+ }
24
+ }
25
+ const source = entry[config.sourceLocale].trim().normalize('NFC').toLowerCase();
26
+ if (sources.has(source)) return translatorT(t, 'glossary_duplicateSource');
27
+ sources.add(source);
28
+ }
29
+ return true;
30
+ };
31
+ export const validateGlossaryConfig = (config)=>{
32
+ const locales = [
33
+ config.sourceLocale,
34
+ ...config.targetLocales
35
+ ];
36
+ if (!config.targetLocales.length || new Set(locales).size !== locales.length) {
37
+ throw new Error('The glossary requires distinct source and target locales.');
38
+ }
39
+ if (locales.some((locale)=>!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(locale) || [
40
+ '__proto__',
41
+ 'constructor',
42
+ 'createdAt',
43
+ 'id',
44
+ 'prototype',
45
+ 'sourceKey',
46
+ 'updatedAt'
47
+ ].includes(locale))) {
48
+ throw new Error('Use valid Payload field names for glossary locales and map provider language codes in the resolver.');
49
+ }
50
+ };
51
+
52
+ //# sourceMappingURL=glossary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/glossary.ts"],"sourcesContent":["import type { TFunction } from '@payloadcms/translations';\nimport type { Access, CollectionConfig } from 'payload';\n\nimport { translatorT } from './i18n-translations';\n\nexport type GlossaryConfig = {\n access: { read: Access; update: Access };\n label?: NonNullable<CollectionConfig['labels']>['plural'];\n localeLabels?: Record<string, Record<string, string> | string>;\n slug?: string;\n sourceLocale: string;\n targetLocales: string[];\n};\n\nexport const getGlossarySlug = (config: GlossaryConfig) => config.slug ?? 'translation-glossary';\n\nexport const validateGlossaryEntries = (\n value: unknown,\n config: GlossaryConfig,\n t?: TFunction,\n): string | true => {\n if (!Array.isArray(value) || !value.length) return translatorT(t, 'glossary_empty');\n\n const locales = [config.sourceLocale, ...config.targetLocales];\n\n const sources = new Set<string>();\n\n for (const entry of value) {\n for (const locale of locales) {\n const term = entry?.[locale];\n\n if (\n locale !== config.sourceLocale &&\n (term == null || (typeof term === 'string' && !term.trim()))\n ) {\n continue;\n }\n if (typeof term !== 'string' || !term.trim() || /[\\t\\r\\n]/.test(term)) {\n return translatorT(t, 'glossary_invalidTerm', {\n locale: config.sourceLocale.toUpperCase(),\n });\n }\n if (Buffer.byteLength(term.trim(), 'utf8') > 1024) {\n return translatorT(t, 'glossary_termTooLong');\n }\n }\n\n const source = entry[config.sourceLocale].trim().normalize('NFC').toLowerCase();\n\n if (sources.has(source)) return translatorT(t, 'glossary_duplicateSource');\n sources.add(source);\n }\n\n return true;\n};\n\nexport const validateGlossaryConfig = (config: GlossaryConfig): void => {\n const locales = [config.sourceLocale, ...config.targetLocales];\n\n if (!config.targetLocales.length || new Set(locales).size !== locales.length) {\n throw new Error('The glossary requires distinct source and target locales.');\n }\n if (\n locales.some(\n (locale) =>\n !/^[a-zA-Z][a-zA-Z0-9_]*$/.test(locale) ||\n [\n '__proto__',\n 'constructor',\n 'createdAt',\n 'id',\n 'prototype',\n 'sourceKey',\n 'updatedAt',\n ].includes(locale),\n )\n ) {\n throw new Error(\n 'Use valid Payload field names for glossary locales and map provider language codes in the resolver.',\n );\n }\n};\n"],"names":["translatorT","getGlossarySlug","config","slug","validateGlossaryEntries","value","t","Array","isArray","length","locales","sourceLocale","targetLocales","sources","Set","entry","locale","term","trim","test","toUpperCase","Buffer","byteLength","source","normalize","toLowerCase","has","add","validateGlossaryConfig","size","Error","some","includes"],"mappings":"AAGA,SAASA,WAAW,QAAQ,sBAAsB;AAWlD,OAAO,MAAMC,kBAAkB,CAACC,SAA2BA,OAAOC,IAAI,IAAI,uBAAuB;AAEjG,OAAO,MAAMC,0BAA0B,CACrCC,OACAH,QACAI;IAEA,IAAI,CAACC,MAAMC,OAAO,CAACH,UAAU,CAACA,MAAMI,MAAM,EAAE,OAAOT,YAAYM,GAAG;IAElE,MAAMI,UAAU;QAACR,OAAOS,YAAY;WAAKT,OAAOU,aAAa;KAAC;IAE9D,MAAMC,UAAU,IAAIC;IAEpB,KAAK,MAAMC,SAASV,MAAO;QACzB,KAAK,MAAMW,UAAUN,QAAS;YAC5B,MAAMO,OAAOF,OAAO,CAACC,OAAO;YAE5B,IACEA,WAAWd,OAAOS,YAAY,IAC7BM,CAAAA,QAAQ,QAAS,OAAOA,SAAS,YAAY,CAACA,KAAKC,IAAI,EAAE,GAC1D;gBACA;YACF;YACA,IAAI,OAAOD,SAAS,YAAY,CAACA,KAAKC,IAAI,MAAM,WAAWC,IAAI,CAACF,OAAO;gBACrE,OAAOjB,YAAYM,GAAG,wBAAwB;oBAC5CU,QAAQd,OAAOS,YAAY,CAACS,WAAW;gBACzC;YACF;YACA,IAAIC,OAAOC,UAAU,CAACL,KAAKC,IAAI,IAAI,UAAU,MAAM;gBACjD,OAAOlB,YAAYM,GAAG;YACxB;QACF;QAEA,MAAMiB,SAASR,KAAK,CAACb,OAAOS,YAAY,CAAC,CAACO,IAAI,GAAGM,SAAS,CAAC,OAAOC,WAAW;QAE7E,IAAIZ,QAAQa,GAAG,CAACH,SAAS,OAAOvB,YAAYM,GAAG;QAC/CO,QAAQc,GAAG,CAACJ;IACd;IAEA,OAAO;AACT,EAAE;AAEF,OAAO,MAAMK,yBAAyB,CAAC1B;IACrC,MAAMQ,UAAU;QAACR,OAAOS,YAAY;WAAKT,OAAOU,aAAa;KAAC;IAE9D,IAAI,CAACV,OAAOU,aAAa,CAACH,MAAM,IAAI,IAAIK,IAAIJ,SAASmB,IAAI,KAAKnB,QAAQD,MAAM,EAAE;QAC5E,MAAM,IAAIqB,MAAM;IAClB;IACA,IACEpB,QAAQqB,IAAI,CACV,CAACf,SACC,CAAC,0BAA0BG,IAAI,CAACH,WAChC;YACE;YACA;YACA;YACA;YACA;YACA;YACA;SACD,CAACgB,QAAQ,CAAChB,UAEf;QACA,MAAM,IAAIc,MACR;IAEJ;AACF,EAAE"}
@@ -0,0 +1,5 @@
1
+ import type { CollectionConfig, PayloadRequest } from 'payload';
2
+ import type { GlossaryConfig } from './glossary';
3
+ export declare const getGlossaryEntriesSlug: (config: GlossaryConfig) => string;
4
+ export declare const createGlossaryEntriesCollection: (config: GlossaryConfig, basePath: string, sync: (req: PayloadRequest, entry: Record<string, string>) => Promise<void>) => CollectionConfig;
5
+ //# sourceMappingURL=glossaryCollection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glossaryCollection.d.ts","sourceRoot":"","sources":["../src/glossaryCollection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD,eAAO,MAAM,sBAAsB,WAAY,cAAc,WACvB,CAAC;AAKvC,eAAO,MAAM,+BAA+B,WAClC,cAAc,YACZ,MAAM,QACV,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAC1E,gBAkFF,CAAC"}