@pigment/auto-translate 1.3.3 → 1.4.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.
package/README.md CHANGED
@@ -401,6 +401,54 @@ For more detailed information, check out these guides:
401
401
 
402
402
  ---
403
403
 
404
+ ## 🌐 Edge Middleware (locale detection)
405
+
406
+ This plugin's translation logic runs inside Payload `afterOperation` hooks on the **Node.js server** and is fully edge-compatible on the _plugin side_ — no changes are needed in the plugin itself.
407
+
408
+ If your **Next.js app** needs locale-based routing (detect locale from `Accept-Language` and redirect), you can add middleware.
409
+
410
+ ### Next.js 16 — middleware vs proxy
411
+
412
+ | Convention | Runtime | Next.js version |
413
+ |---|---|---|
414
+ | `middleware.ts` (legacy name, still supported) | **Edge** (default) | 15 + 16 |
415
+ | `proxy.ts` (new name in v16) | **Node.js only** | 16 only |
416
+
417
+ In Next.js 16 the `proxy` convention (Node.js runtime) is recommended, but **Edge runtime requires keeping the `middleware.ts` filename**. A ready-to-use example for both approaches is provided in `dev/middleware.ts`.
418
+
419
+ ### Quick example (Edge, locale detection)
420
+
421
+ ```ts
422
+ // middleware.ts (at the root of your Next.js project)
423
+ import type { NextRequest } from 'next/server'
424
+ import { NextResponse } from 'next/server'
425
+
426
+ const LOCALES = ['sv', 'en', 'de']
427
+ const DEFAULT_LOCALE = 'sv'
428
+
429
+ export function middleware(request: NextRequest) {
430
+ const { pathname } = request.nextUrl
431
+ const hasLocale = LOCALES.some(
432
+ (l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`,
433
+ )
434
+ if (hasLocale) return NextResponse.next()
435
+
436
+ const accept = request.headers.get('accept-language') ?? ''
437
+ const locale = LOCALES.find((l) => accept.toLowerCase().includes(l)) ?? DEFAULT_LOCALE
438
+ const url = request.nextUrl.clone()
439
+ url.pathname = `/${locale}${pathname}`
440
+ return NextResponse.redirect(url)
441
+ }
442
+
443
+ export const config = {
444
+ matcher: ['/((?!payload|api|_next|favicon\\.ico).*)'],
445
+ // NOTE: do NOT add runtime: 'edge' here — it's for Route Segments, not middleware.
446
+ // Edge is the implicit default for middleware.ts in Next.js 16.
447
+ }
448
+ ```
449
+
450
+ ---
451
+
404
452
  ## 🤝 Contributing
405
453
 
406
454
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -1,10 +1,10 @@
1
1
  export const getTranslationExclusionsCollection = (slug = 'translation-exclusions')=>({
2
2
  slug,
3
3
  access: {
4
- create: ()=>true,
5
- delete: ()=>true,
6
- read: ()=>true,
7
- update: ()=>true
4
+ create: ({ req: { user } })=>Boolean(user),
5
+ delete: ({ req: { user } })=>Boolean(user),
6
+ read: ({ req: { user } })=>Boolean(user),
7
+ update: ({ req: { user } })=>Boolean(user)
8
8
  },
9
9
  admin: {
10
10
  defaultColumns: [
@@ -14,7 +14,7 @@ export const getTranslationExclusionsCollection = (slug = 'translation-exclusion
14
14
  'excludedPaths'
15
15
  ],
16
16
  description: 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (collectionSlug, documentId, locale) combination.',
17
- group: 'Settings',
17
+ group: 'Auto-Translate Settings',
18
18
  useAsTitle: 'collectionSlug'
19
19
  },
20
20
  fields: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collections/translationExclusions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const getTranslationExclusionsCollection = (\n slug: string = 'translation-exclusions',\n): CollectionConfig => ({\n slug,\n access: {\n create: () => true,\n delete: () => true,\n read: () => true,\n update: () => true,\n },\n admin: {\n defaultColumns: ['collectionSlug', 'documentId', 'locale', 'excludedPaths'],\n description:\n 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (collectionSlug, documentId, locale) combination.',\n group: 'Settings',\n useAsTitle: 'collectionSlug',\n },\n fields: [\n {\n name: 'collectionSlug',\n type: 'text',\n admin: {\n description: 'The collection this exclusion belongs to',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'documentId',\n type: 'text',\n admin: {\n description: 'The ID of the document',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'locale',\n type: 'text',\n admin: {\n description: 'The locale these exclusions apply to (e.g., \"en\", \"de\", \"fr\")',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n label: 'Locale',\n required: true,\n },\n {\n name: 'excludedPaths',\n type: 'array',\n admin: {\n description:\n 'Fields that should NOT be auto-translated in this specific locale. Each locale has its own independent set of exclusions.',\n },\n fields: [\n {\n name: 'path',\n type: 'text',\n admin: {\n description: 'Field path (e.g., \"title\", \"content.0.description\")',\n },\n label: 'Field Path',\n required: true,\n },\n ],\n label: `Excluded Fields for this Locale`,\n required: true,\n },\n ],\n})\n"],"names":["getTranslationExclusionsCollection","slug","access","create","delete","read","update","admin","defaultColumns","description","group","useAsTitle","fields","name","type","position","readOnly","index","required","label"],"mappings":"AAEA,OAAO,MAAMA,qCAAqC,CAChDC,OAAe,wBAAwB,GACjB,CAAA;QACtBA;QACAC,QAAQ;YACNC,QAAQ,IAAM;YACdC,QAAQ,IAAM;YACdC,MAAM,IAAM;YACZC,QAAQ,IAAM;QAChB;QACAC,OAAO;YACLC,gBAAgB;gBAAC;gBAAkB;gBAAc;gBAAU;aAAgB;YAC3EC,aACE;YACFC,OAAO;YACPC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPE,OAAO;gBACPD,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aACE;gBACJ;gBACAG,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLE,aAAa;wBACf;wBACAU,OAAO;wBACPD,UAAU;oBACZ;iBACD;gBACDC,OAAO,CAAC,+BAA+B,CAAC;gBACxCD,UAAU;YACZ;SACD;IACH,CAAA,EAAE"}
1
+ {"version":3,"sources":["../../src/collections/translationExclusions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const getTranslationExclusionsCollection = (\n slug: string = 'translation-exclusions',\n): CollectionConfig => ({\n slug,\n access: {\n create: ({ req: { user } }) => Boolean(user),\n delete: ({ req: { user } }) => Boolean(user),\n read: ({ req: { user } }) => Boolean(user),\n update: ({ req: { user } }) => Boolean(user),\n },\n admin: {\n defaultColumns: ['collectionSlug', 'documentId', 'locale', 'excludedPaths'],\n description:\n 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (collectionSlug, documentId, locale) combination.',\n group: 'Auto-Translate Settings',\n useAsTitle: 'collectionSlug',\n },\n fields: [\n {\n name: 'collectionSlug',\n type: 'text',\n admin: {\n description: 'The collection this exclusion belongs to',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'documentId',\n type: 'text',\n admin: {\n description: 'The ID of the document',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'locale',\n type: 'text',\n admin: {\n description: 'The locale these exclusions apply to (e.g., \"en\", \"de\", \"fr\")',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n label: 'Locale',\n required: true,\n },\n {\n name: 'excludedPaths',\n type: 'array',\n admin: {\n description:\n 'Fields that should NOT be auto-translated in this specific locale. Each locale has its own independent set of exclusions.',\n },\n fields: [\n {\n name: 'path',\n type: 'text',\n admin: {\n description: 'Field path (e.g., \"title\", \"content.0.description\")',\n },\n label: 'Field Path',\n required: true,\n },\n ],\n label: `Excluded Fields for this Locale`,\n required: true,\n },\n ],\n})\n"],"names":["getTranslationExclusionsCollection","slug","access","create","req","user","Boolean","delete","read","update","admin","defaultColumns","description","group","useAsTitle","fields","name","type","position","readOnly","index","required","label"],"mappings":"AAEA,OAAO,MAAMA,qCAAqC,CAChDC,OAAe,wBAAwB,GACjB,CAAA;QACtBA;QACAC,QAAQ;YACNC,QAAQ,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;YACvCE,QAAQ,CAAC,EAAEH,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;YACvCG,MAAM,CAAC,EAAEJ,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;YACrCI,QAAQ,CAAC,EAAEL,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;QACzC;QACAK,OAAO;YACLC,gBAAgB;gBAAC;gBAAkB;gBAAc;gBAAU;aAAgB;YAC3EC,aACE;YACFC,OAAO;YACPC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPE,OAAO;gBACPD,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aACE;gBACJ;gBACAG,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLE,aAAa;wBACf;wBACAU,OAAO;wBACPD,UAAU;oBACZ;iBACD;gBACDC,OAAO,CAAC,+BAA+B,CAAC;gBACxCD,UAAU;YACZ;SACD;IACH,CAAA,EAAE"}
@@ -1,14 +1,8 @@
1
1
  export const getTranslationSettingsGlobal = (slug = 'translation-settings')=>({
2
2
  slug,
3
- access: {
4
- read: ()=>true,
5
- update: ()=>{
6
- // Only admins can update translation settings
7
- return true;
8
- }
9
- },
10
3
  admin: {
11
- description: 'Configure translation settings including the system prompt and model parameters'
4
+ description: 'Configure translation settings including the system prompt and model parameters',
5
+ group: 'Auto-Translate Settings'
12
6
  },
13
7
  fields: [
14
8
  {
@@ -19,8 +13,7 @@ export const getTranslationSettingsGlobal = (slug = 'translation-settings')=>({
19
13
  Field: '@pigment/auto-translate/client#LockTranslation'
20
14
  },
21
15
  position: 'sidebar'
22
- },
23
- custom: {}
16
+ }
24
17
  },
25
18
  {
26
19
  name: 'lockTranslationSettings',
@@ -61,12 +54,12 @@ export const getTranslationSettingsGlobal = (slug = 'translation-settings')=>({
61
54
  rows: 8
62
55
  },
63
56
  defaultValue: `Rules:
64
- - Only translate the values, never the keys
65
- - Preserve the exact JSON structure
66
- - Do not translate field names like 'id', 'createdAt', 'updatedAt', etc.
67
- - Maintain formatting, HTML tags, and special characters
68
- - Return only valid JSON without any markdown formatting or code blocks
69
- - If a value is already in the target language or is a proper noun, keep it as is`,
57
+ - Only translate the values, never the keys
58
+ - Preserve the exact JSON structure
59
+ - Do not translate field names like 'id', 'createdAt', 'updatedAt', etc.
60
+ - Maintain formatting, HTML tags, and special characters
61
+ - Return only valid JSON without any markdown formatting or code blocks
62
+ - If a value is already in the target language or is a proper noun, keep it as is`,
70
63
  label: 'Translation Rules',
71
64
  required: true
72
65
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/globals/translationSettings.ts"],"sourcesContent":["import type { GlobalConfig } from 'payload'\n\nexport const getTranslationSettingsGlobal = (\n slug: string = 'translation-settings',\n): GlobalConfig => ({\n slug,\n access: {\n read: () => true,\n update: () => {\n // Only admins can update translation settings\n return true\n },\n },\n admin: {\n description: 'Configure translation settings including the system prompt and model parameters',\n },\n fields: [\n {\n name: 'settingsLock',\n type: 'ui',\n admin: {\n components: {\n Field: '@pigment/auto-translate/client#LockTranslation',\n },\n position: 'sidebar',\n },\n custom: {},\n },\n {\n name: 'lockTranslationSettings',\n type: 'checkbox',\n admin: {\n hidden: true,\n },\n defaultValue: true,\n },\n {\n name: 'systemPrompt',\n type: 'textarea',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description:\n 'The main instruction for the AI translator. Use {fromLocale} and {toLocale} as placeholders.',\n rows: 3,\n },\n defaultValue: `You are a professional translator. Translate the JSON object values from {fromLocale} to {toLocale}.`,\n label: 'System Prompt',\n required: true,\n },\n {\n name: 'translationRules',\n type: 'textarea',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description:\n \"⚠️ Do not edit if you don't know what you are doing. These rules ensure proper JSON translation behavior.\",\n rows: 8,\n },\n defaultValue: `Rules:\n - Only translate the values, never the keys\n - Preserve the exact JSON structure\n - Do not translate field names like 'id', 'createdAt', 'updatedAt', etc.\n - Maintain formatting, HTML tags, and special characters\n - Return only valid JSON without any markdown formatting or code blocks\n - If a value is already in the target language or is a proper noun, keep it as is`,\n label: 'Translation Rules',\n required: true,\n },\n {\n name: 'model',\n type: 'text',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description: 'The OpenAI model to use for translations (e.g., gpt-4o, gpt-4o-mini)',\n },\n defaultValue: 'gpt-4o',\n label: 'Model',\n required: true,\n },\n {\n name: 'temperature',\n type: 'number',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description:\n 'Controls randomness in translation (0.0-2.0). Lower values are more deterministic.',\n step: 0.1,\n },\n defaultValue: 0.3,\n label: 'Temperature',\n max: 2,\n min: 0,\n required: true,\n },\n {\n name: 'maxTokens',\n type: 'number',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description: 'Maximum tokens for the response. Leave empty for automatic.',\n },\n label: 'Max Tokens',\n min: 1,\n },\n ],\n hooks: {\n afterChange: [\n async ({ data, req }) => {\n if (!data?.lockTranslationSettings) {\n const result = await req.payload.updateGlobal({\n slug: 'translation-settings',\n data: { lockTranslationSettings: true },\n req,\n })\n\n return result\n }\n },\n ],\n },\n label: 'Translation Settings',\n})\n"],"names":["getTranslationSettingsGlobal","slug","access","read","update","admin","description","fields","name","type","components","Field","position","custom","hidden","defaultValue","data","lockTranslationSettings","rows","label","required","step","max","min","hooks","afterChange","req","result","payload","updateGlobal"],"mappings":"AAEA,OAAO,MAAMA,+BAA+B,CAC1CC,OAAe,sBAAsB,GACnB,CAAA;QAClBA;QACAC,QAAQ;YACNC,MAAM,IAAM;YACZC,QAAQ;gBACN,8CAA8C;gBAC9C,OAAO;YACT;QACF;QACAC,OAAO;YACLC,aAAa;QACf;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNJ,OAAO;oBACLK,YAAY;wBACVC,OAAO;oBACT;oBACAC,UAAU;gBACZ;gBACAC,QAAQ,CAAC;YACX;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNJ,OAAO;oBACLS,QAAQ;gBACV;gBACAC,cAAc;YAChB;YACA;gBACEP,MAAM;gBACNC,MAAM;gBACNP,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEY,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAZ,OAAO;oBACLC,aACE;oBACFY,MAAM;gBACR;gBACAH,cAAc,CAAC,oGAAoG,CAAC;gBACpHI,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEZ,MAAM;gBACNC,MAAM;gBACNP,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEY,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAZ,OAAO;oBACLC,aACE;oBACFY,MAAM;gBACR;gBACAH,cAAc,CAAC;;;;;;qFAMgE,CAAC;gBAChFI,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEZ,MAAM;gBACNC,MAAM;gBACNP,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEY,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAZ,OAAO;oBACLC,aAAa;gBACf;gBACAS,cAAc;gBACdI,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEZ,MAAM;gBACNC,MAAM;gBACNP,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEY,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAZ,OAAO;oBACLC,aACE;oBACFe,MAAM;gBACR;gBACAN,cAAc;gBACdI,OAAO;gBACPG,KAAK;gBACLC,KAAK;gBACLH,UAAU;YACZ;YACA;gBACEZ,MAAM;gBACNC,MAAM;gBACNP,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEY,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAZ,OAAO;oBACLC,aAAa;gBACf;gBACAa,OAAO;gBACPI,KAAK;YACP;SACD;QACDC,OAAO;YACLC,aAAa;gBACX,OAAO,EAAET,IAAI,EAAEU,GAAG,EAAE;oBAClB,IAAI,CAACV,MAAMC,yBAAyB;wBAClC,MAAMU,SAAS,MAAMD,IAAIE,OAAO,CAACC,YAAY,CAAC;4BAC5C5B,MAAM;4BACNe,MAAM;gCAAEC,yBAAyB;4BAAK;4BACtCS;wBACF;wBAEA,OAAOC;oBACT;gBACF;aACD;QACH;QACAR,OAAO;IACT,CAAA,EAAE"}
1
+ {"version":3,"sources":["../../src/globals/translationSettings.ts"],"sourcesContent":["import type { GlobalConfig } from 'payload'\n\nexport const getTranslationSettingsGlobal = (\n slug: string = 'translation-settings',\n): GlobalConfig => ({\n slug,\n admin: {\n description: 'Configure translation settings including the system prompt and model parameters',\n group: 'Auto-Translate Settings',\n },\n fields: [\n {\n name: 'settingsLock',\n type: 'ui',\n admin: {\n components: {\n Field: '@pigment/auto-translate/client#LockTranslation',\n },\n position: 'sidebar',\n },\n },\n {\n name: 'lockTranslationSettings',\n type: 'checkbox',\n admin: {\n hidden: true,\n },\n defaultValue: true,\n },\n {\n name: 'systemPrompt',\n type: 'textarea',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description:\n 'The main instruction for the AI translator. Use {fromLocale} and {toLocale} as placeholders.',\n rows: 3,\n },\n defaultValue: `You are a professional translator. Translate the JSON object values from {fromLocale} to {toLocale}.`,\n label: 'System Prompt',\n required: true,\n },\n {\n name: 'translationRules',\n type: 'textarea',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description:\n \"⚠️ Do not edit if you don't know what you are doing. These rules ensure proper JSON translation behavior.\",\n rows: 8,\n },\n defaultValue: `Rules:\n- Only translate the values, never the keys\n- Preserve the exact JSON structure\n- Do not translate field names like 'id', 'createdAt', 'updatedAt', etc.\n- Maintain formatting, HTML tags, and special characters\n- Return only valid JSON without any markdown formatting or code blocks\n- If a value is already in the target language or is a proper noun, keep it as is`,\n label: 'Translation Rules',\n required: true,\n },\n {\n name: 'model',\n type: 'text',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description: 'The OpenAI model to use for translations (e.g., gpt-4o, gpt-4o-mini)',\n },\n defaultValue: 'gpt-4o',\n label: 'Model',\n required: true,\n },\n {\n name: 'temperature',\n type: 'number',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description:\n 'Controls randomness in translation (0.0-2.0). Lower values are more deterministic.',\n step: 0.1,\n },\n defaultValue: 0.3,\n label: 'Temperature',\n max: 2,\n min: 0,\n required: true,\n },\n {\n name: 'maxTokens',\n type: 'number',\n access: {\n read: () => true,\n update: ({ data }) => {\n return !data?.lockTranslationSettings\n },\n },\n admin: {\n description: 'Maximum tokens for the response. Leave empty for automatic.',\n },\n label: 'Max Tokens',\n min: 1,\n },\n ],\n hooks: {\n afterChange: [\n async ({ data, req }) => {\n if (!data?.lockTranslationSettings) {\n const result = await req.payload.updateGlobal({\n slug: 'translation-settings',\n data: { lockTranslationSettings: true },\n req,\n })\n\n return result\n }\n },\n ],\n },\n label: 'Translation Settings',\n})\n"],"names":["getTranslationSettingsGlobal","slug","admin","description","group","fields","name","type","components","Field","position","hidden","defaultValue","access","read","update","data","lockTranslationSettings","rows","label","required","step","max","min","hooks","afterChange","req","result","payload","updateGlobal"],"mappings":"AAEA,OAAO,MAAMA,+BAA+B,CAC1CC,OAAe,sBAAsB,GACnB,CAAA;QAClBA;QACAC,OAAO;YACLC,aAAa;YACbC,OAAO;QACT;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNL,OAAO;oBACLM,YAAY;wBACVC,OAAO;oBACT;oBACAC,UAAU;gBACZ;YACF;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNL,OAAO;oBACLS,QAAQ;gBACV;gBACAC,cAAc;YAChB;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNM,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEC,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAf,OAAO;oBACLC,aACE;oBACFe,MAAM;gBACR;gBACAN,cAAc,CAAC,oGAAoG,CAAC;gBACpHO,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEd,MAAM;gBACNC,MAAM;gBACNM,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEC,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAf,OAAO;oBACLC,aACE;oBACFe,MAAM;gBACR;gBACAN,cAAc,CAAC;;;;;;iFAM4D,CAAC;gBAC5EO,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEd,MAAM;gBACNC,MAAM;gBACNM,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEC,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAf,OAAO;oBACLC,aAAa;gBACf;gBACAS,cAAc;gBACdO,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEd,MAAM;gBACNC,MAAM;gBACNM,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEC,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAf,OAAO;oBACLC,aACE;oBACFkB,MAAM;gBACR;gBACAT,cAAc;gBACdO,OAAO;gBACPG,KAAK;gBACLC,KAAK;gBACLH,UAAU;YACZ;YACA;gBACEd,MAAM;gBACNC,MAAM;gBACNM,QAAQ;oBACNC,MAAM,IAAM;oBACZC,QAAQ,CAAC,EAAEC,IAAI,EAAE;wBACf,OAAO,CAACA,MAAMC;oBAChB;gBACF;gBACAf,OAAO;oBACLC,aAAa;gBACf;gBACAgB,OAAO;gBACPI,KAAK;YACP;SACD;QACDC,OAAO;YACLC,aAAa;gBACX,OAAO,EAAET,IAAI,EAAEU,GAAG,EAAE;oBAClB,IAAI,CAACV,MAAMC,yBAAyB;wBAClC,MAAMU,SAAS,MAAMD,IAAIE,OAAO,CAACC,YAAY,CAAC;4BAC5C5B,MAAM;4BACNe,MAAM;gCAAEC,yBAAyB;4BAAK;4BACtCS;wBACF;wBAEA,OAAOC;oBACT;gBACF;aACD;QACH;QACAR,OAAO;IACT,CAAA,EAAE"}
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export { getTranslationExclusionsCollection } from './collections/translationExc
4
4
  export { getTranslationSettingsGlobal } from './globals/translationSettings.js';
5
5
  export { TranslationService } from './services/translationService.js';
6
6
  export * from './types/index.js';
7
- export declare const autoTranslate: (pluginOptions: AutoTranslateConfig) => (config: Config) => Config;
7
+ export declare const autoTranslate: (pluginOptions: AutoTranslateConfig) => (incomingConfig: Config) => Config;
package/dist/index.js CHANGED
@@ -6,7 +6,63 @@ export { getTranslationExclusionsCollection } from './collections/translationExc
6
6
  export { getTranslationSettingsGlobal } from './globals/translationSettings.js';
7
7
  export { TranslationService } from './services/translationService.js';
8
8
  export * from './types/index.js';
9
- export const autoTranslate = (pluginOptions)=>(config)=>{
9
+ // Fields that must never be passed as data to payload.update / payload.create
10
+ // (Postgres/drizzle rejects them; MongoDB silently ignores them)
11
+ const SYSTEM_FIELDS = new Set([
12
+ 'id',
13
+ 'createdAt',
14
+ 'updatedAt',
15
+ '_status',
16
+ '__v',
17
+ 'globalType',
18
+ 'updatedBy'
19
+ ]);
20
+ function stripSystemFields(data) {
21
+ const result = {};
22
+ for (const [key, value] of Object.entries(data)){
23
+ if (!SYSTEM_FIELDS.has(key)) {
24
+ result[key] = value;
25
+ }
26
+ }
27
+ return result;
28
+ }
29
+ /**
30
+ * Strips `id` from objects that are direct elements of arrays, recursively
31
+ * through the data tree. This prevents Postgres unique-constraint violations
32
+ * when inserting locale-specific rows into array tables (e.g. posts_content)
33
+ * that share a single PRIMARY KEY on `id` across all locales.
34
+ *
35
+ * Relationship objects (plain objects that are NOT direct array items) keep
36
+ * their `id` so that Payload can still resolve them correctly.
37
+ */ function stripArrayItemIds(data) {
38
+ if (Array.isArray(data)) {
39
+ return data.map((item)=>{
40
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
41
+ // Direct array item — strip its Payload-internal `id`
42
+ const { id: _id, ...rest } = item;
43
+ const processed = {};
44
+ for (const [key, value] of Object.entries(rest)){
45
+ processed[key] = stripArrayItemIds(value);
46
+ }
47
+ return processed;
48
+ }
49
+ return stripArrayItemIds(item);
50
+ });
51
+ }
52
+ if (data && typeof data === 'object') {
53
+ const result = {};
54
+ for (const [key, value] of Object.entries(data)){
55
+ result[key] = stripArrayItemIds(value);
56
+ }
57
+ return result;
58
+ }
59
+ return data;
60
+ }
61
+ export const autoTranslate = (pluginOptions)=>(incomingConfig)=>{
62
+ // Create a shallow copy so we never mutate the caller's config object
63
+ const config = {
64
+ ...incomingConfig
65
+ };
10
66
  // If the plugin is disabled, return config immediately without any modifications
11
67
  if (pluginOptions.disabled) {
12
68
  if (pluginOptions.debugging) {
@@ -14,10 +70,6 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
14
70
  }
15
71
  return config;
16
72
  }
17
- // Validate configuration
18
- if (!config.collections) {
19
- config.collections = [];
20
- }
21
73
  if (!config.localization) {
22
74
  console.warn('[Auto-Translate Plugin] No localization config found. Plugin will not function properly.');
23
75
  return config;
@@ -35,21 +87,32 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
35
87
  console.log('- Exclusions enabled:', enableExclusions);
36
88
  }
37
89
  // Add translation exclusions collection (only if exclusions are enabled)
90
+ // Use spread to avoid mutating the original array
38
91
  if (enableExclusions) {
39
92
  const exclusionsSlug = pluginOptions.translationExclusionsSlug || 'translation-exclusions';
40
- config.collections.push(getTranslationExclusionsCollection(exclusionsSlug));
41
- }
42
- // Add translation settings global
43
- if (!config.globals) {
44
- config.globals = [];
93
+ config.collections = [
94
+ ...config.collections || [],
95
+ getTranslationExclusionsCollection(exclusionsSlug)
96
+ ];
97
+ } else {
98
+ config.collections = [
99
+ ...config.collections || []
100
+ ];
45
101
  }
102
+ // Add translation settings global using spread
46
103
  const settingsSlug = pluginOptions.translationSettingsSlug || 'translation-settings';
47
- config.globals.push(getTranslationSettingsGlobal(settingsSlug));
104
+ config.globals = [
105
+ ...config.globals || [],
106
+ getTranslationSettingsGlobal(settingsSlug)
107
+ ];
48
108
  // Initialize translation service
49
109
  const translationService = new TranslationService(pluginOptions);
50
110
  // Configure collections with auto-translate
51
111
  if (pluginOptions.collections) {
52
- for(const collectionSlug in pluginOptions.collections){
112
+ for(const rawSlug in pluginOptions.collections){
113
+ // Payload 3.85+ requires CollectionSlug (strict union), but for...in
114
+ // yields string. Cast once here and use collectionSlug throughout.
115
+ const collectionSlug = rawSlug;
53
116
  const collectionConfig = pluginOptions.collections[collectionSlug];
54
117
  // Skip if disabled
55
118
  if (collectionConfig === false || typeof collectionConfig === 'object' && collectionConfig.enabled === false) {
@@ -61,16 +124,19 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
61
124
  continue;
62
125
  }
63
126
  // Add translationSync field to collection
64
- collection.fields.push({
65
- name: 'translationSync',
66
- type: 'checkbox',
67
- admin: {
68
- description: 'When enabled, changes in the default language will automatically translate to other languages',
69
- position: 'sidebar'
70
- },
71
- defaultValue: pluginOptions.enableTranslationSyncByDefault ?? true,
72
- label: 'Enable Auto-Translation'
73
- });
127
+ collection.fields = [
128
+ ...collection.fields,
129
+ {
130
+ name: 'translationSync',
131
+ type: 'checkbox',
132
+ admin: {
133
+ description: 'When enabled, changes in the default language will automatically translate to other languages',
134
+ position: 'sidebar'
135
+ },
136
+ defaultValue: pluginOptions.enableTranslationSyncByDefault ?? true,
137
+ label: 'Enable Auto-Translation'
138
+ }
139
+ ];
74
140
  // Auto-inject TranslationControl component into all localized fields
75
141
  // Only inject if exclusions are enabled (otherwise there's nothing to control)
76
142
  if (enableExclusions && pluginOptions.autoInjectUI !== false) {
@@ -87,15 +153,15 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
87
153
  collection.hooks.afterOperation = [];
88
154
  }
89
155
  // Main translation hook
90
- collection.hooks.afterOperation.push(async ({ operation, req, result })=>{
91
- // Only process create and update operations
156
+ const translationHook = async ({ operation, req, result })=>{
157
+ // Only process create and updateByID operations
92
158
  if (operation !== 'create' && operation !== 'updateByID') {
93
159
  if (pluginOptions.debugging) {
94
160
  req.payload.logger.error(`[Auto-Translate Plugin] Skipping translation - not create or update operation: ${operation}`);
95
161
  }
96
162
  return result;
97
163
  }
98
- // For create/update operations, result should have a id property
164
+ // For create/update operations, result should have an id property
99
165
  if (!result || typeof result !== 'object' || !('id' in result)) {
100
166
  if (pluginOptions.debugging) {
101
167
  req.payload.logger.error(`[Auto-Translate Plugin] No document found in result: ${JSON.stringify(result)}`);
@@ -179,7 +245,9 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
179
245
  toLocale: targetLocale
180
246
  });
181
247
  // Merge translated data with existing, preserving excluded fields
182
- const finalData = translatedData;
248
+ let finalData = {
249
+ ...translatedData
250
+ };
183
251
  if (existingDoc && allExcludedPaths.length > 0) {
184
252
  // Preserve excluded fields from existing document
185
253
  for (const excludedPath of allExcludedPaths){
@@ -189,11 +257,22 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
189
257
  }
190
258
  }
191
259
  }
260
+ // Strip system/internal fields before updating so Postgres adapter
261
+ // does not receive `id`, `createdAt`, `updatedAt`, etc. as data fields.
262
+ // MongoDB is lenient with extra fields; Postgres/drizzle raises
263
+ // ValidationError: The following field is invalid: id
264
+ //
265
+ // Also strip `id` from nested array items: Payload's array tables
266
+ // (e.g. posts_content) have a shared PRIMARY KEY on `id` across all
267
+ // locales, so reusing source-locale item IDs for a target locale causes
268
+ // a Postgres 23505 unique-constraint violation.
269
+ const strippedArrayIds = stripArrayItemIds(finalData);
270
+ const updateData = stripSystemFields(strippedArrayIds);
192
271
  // Update the document in the target locale
193
272
  await req.payload.update({
194
273
  id: doc.id,
195
274
  collection: collectionSlug,
196
- data: finalData,
275
+ data: updateData,
197
276
  locale: targetLocale,
198
277
  // Prevent infinite loop - don't trigger hooks
199
278
  context: {
@@ -208,14 +287,6 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
208
287
  // Log detailed error information
209
288
  const errorMessage = error instanceof Error ? error.message : String(error);
210
289
  const errorStack = error instanceof Error ? error.stack : undefined;
211
- const errorDetails = {
212
- collection: collectionSlug,
213
- documentId: doc.id,
214
- fromLocale: defaultLocale,
215
- message: errorMessage,
216
- stack: errorStack,
217
- toLocale: targetLocale
218
- };
219
290
  req.payload.logger.error(`[Auto-Translate Plugin] Error translating ${collectionSlug}:${doc.id} to ${targetLocale}:`);
220
291
  req.payload.logger.error(errorMessage);
221
292
  if (pluginOptions.debugging && errorStack) {
@@ -231,23 +302,24 @@ export const autoTranslate = (pluginOptions)=>(config)=>{
231
302
  }
232
303
  }
233
304
  return result;
234
- });
305
+ };
235
306
  // Prevent infinite loops - skip translation if triggered by our own update
236
- const originalAfterOperationHooks = [
237
- ...collection.hooks.afterOperation || []
307
+ // Wrap ALL afterOperation hooks so the skipAutoTranslate context is checked first
308
+ const existingHooks = [
309
+ ...collection.hooks.afterOperation || [],
310
+ translationHook
238
311
  ];
239
312
  collection.hooks.afterOperation = [
240
313
  async (args)=>{
241
314
  // Skip if this update was triggered by auto-translate
242
- // Context might not be available on all operations
243
315
  if ('req' in args && args.req?.context?.skipAutoTranslate) {
244
316
  return args.result;
245
317
  }
246
318
  // Run all hooks including translation
247
- for (const hook of originalAfterOperationHooks){
248
- const result = await hook(args);
249
- if (result !== undefined) {
250
- args.result = result;
319
+ for (const hook of existingHooks){
320
+ const hookResult = await hook(args);
321
+ if (hookResult !== undefined) {
322
+ args.result = hookResult;
251
323
  }
252
324
  }
253
325
  return args.result;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AutoTranslateConfig } from './types/index.js'\n\nimport { getTranslationExclusionsCollection } from './collections/translationExclusions.js'\nimport { getTranslationSettingsGlobal } from './globals/translationSettings.js'\nimport { TranslationService } from './services/translationService.js'\nimport { injectTranslationControls } from './utilities/injectTranslationControls.js'\n\nexport { getTranslationExclusionsCollection } from './collections/translationExclusions.js'\nexport { getTranslationSettingsGlobal } from './globals/translationSettings.js'\nexport { TranslationService } from './services/translationService.js'\nexport * from './types/index.js'\n\nexport const autoTranslate =\n (pluginOptions: AutoTranslateConfig) =>\n (config: Config): Config => {\n // If the plugin is disabled, return config immediately without any modifications\n if (pluginOptions.disabled) {\n if (pluginOptions.debugging) {\n console.log('[Auto-Translate Plugin] Plugin is disabled, skipping all modifications')\n }\n return config\n }\n\n // Validate configuration\n if (!config.collections) {\n config.collections = []\n }\n\n if (!config.localization) {\n console.warn(\n '[Auto-Translate Plugin] No localization config found. Plugin will not function properly.',\n )\n return config\n }\n\n const localizationConfig = config.localization\n const defaultLocale = localizationConfig.defaultLocale\n const allLocales = Array.isArray(localizationConfig.locales)\n ? localizationConfig.locales.map((l) => (typeof l === 'string' ? l : l.code))\n : []\n\n // Default enableExclusions to true for backward compatibility\n const enableExclusions = pluginOptions.enableExclusions !== false\n\n if (pluginOptions.debugging) {\n console.log('[Auto-Translate Plugin] Configuration:')\n console.log('- Default locale:', defaultLocale)\n console.log('- All locales:', allLocales)\n console.log('- Enabled collections:', Object.keys(pluginOptions.collections || {}))\n console.log('- Exclusions enabled:', enableExclusions)\n }\n\n // Add translation exclusions collection (only if exclusions are enabled)\n if (enableExclusions) {\n const exclusionsSlug = pluginOptions.translationExclusionsSlug || 'translation-exclusions'\n config.collections.push(getTranslationExclusionsCollection(exclusionsSlug))\n }\n\n // Add translation settings global\n if (!config.globals) {\n config.globals = []\n }\n const settingsSlug = pluginOptions.translationSettingsSlug || 'translation-settings'\n config.globals.push(getTranslationSettingsGlobal(settingsSlug))\n\n // Initialize translation service\n const translationService = new TranslationService(pluginOptions)\n\n // Configure collections with auto-translate\n if (pluginOptions.collections) {\n for (const collectionSlug in pluginOptions.collections) {\n const collectionConfig = pluginOptions.collections[collectionSlug]\n\n // Skip if disabled\n if (\n collectionConfig === false ||\n (typeof collectionConfig === 'object' && collectionConfig.enabled === false)\n ) {\n continue\n }\n\n const collection = config.collections.find((c) => c.slug === collectionSlug)\n\n if (!collection) {\n console.warn(`[Auto-Translate Plugin] Collection \"${collectionSlug}\" not found in config`)\n continue\n }\n\n // Add translationSync field to collection\n collection.fields.push({\n name: 'translationSync',\n type: 'checkbox',\n admin: {\n description:\n 'When enabled, changes in the default language will automatically translate to other languages',\n position: 'sidebar',\n },\n defaultValue: pluginOptions.enableTranslationSyncByDefault ?? true,\n label: 'Enable Auto-Translation',\n })\n\n // Auto-inject TranslationControl component into all localized fields\n // Only inject if exclusions are enabled (otherwise there's nothing to control)\n if (enableExclusions && pluginOptions.autoInjectUI !== false) {\n collection.fields = injectTranslationControls(collection.fields, defaultLocale)\n\n if (pluginOptions.debugging) {\n console.log(`[Auto-Translate Plugin] Auto-injected UI controls for: ${collectionSlug}`)\n }\n }\n\n // Add hooks for translation\n if (!collection.hooks) {\n collection.hooks = {}\n }\n\n if (!collection.hooks.afterOperation) {\n collection.hooks.afterOperation = []\n }\n\n // Main translation hook\n collection.hooks.afterOperation.push(async ({ operation, req, result }) => {\n // Only process create and update operations\n if (operation !== 'create' && operation !== 'updateByID') {\n if (pluginOptions.debugging) {\n req.payload.logger.error(\n `[Auto-Translate Plugin] Skipping translation - not create or update operation: ${operation}`,\n )\n }\n return result\n }\n\n // For create/update operations, result should have a id property\n if (!result || typeof result !== 'object' || !('id' in result)) {\n if (pluginOptions.debugging) {\n req.payload.logger.error(\n `[Auto-Translate Plugin] No document found in result: ${JSON.stringify(result)}`,\n )\n }\n return result\n }\n\n const doc = result\n\n // Only translate if editing from default locale\n if (req.locale !== defaultLocale) {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping translation - not default locale (current: ${req.locale}, default: ${defaultLocale})`,\n )\n }\n return result\n }\n\n // Skip translation for drafts when autosave is enabled\n // Only translate when document is published\n if (doc._status && doc._status !== 'published') {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping translation - document is a draft (status: ${doc._status})`,\n )\n }\n return result\n }\n\n // Check if translation sync is enabled\n if (!doc.translationSync) {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping translation - translationSync disabled for ${collectionSlug}:${doc.id}`,\n )\n }\n return result\n }\n\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Processing ${collectionSlug} document ${operation}: ${doc.id}`,\n )\n }\n\n // Get secondary locales (all locales except default)\n const secondaryLocales = allLocales.filter((locale) => locale !== defaultLocale)\n\n // Translate to each secondary locale\n for (const targetLocale of secondaryLocales) {\n try {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Translating ${collectionSlug}:${doc.id} from ${defaultLocale} to ${targetLocale}`,\n )\n }\n\n // Get field-level exclusions for this locale (only if exclusions are enabled)\n let excludedPaths: string[] = []\n if (enableExclusions) {\n excludedPaths = await translationService.getExclusions(\n req.payload,\n collectionSlug,\n doc.id.toString(),\n targetLocale,\n )\n }\n\n // Get global/collection-level excluded fields\n const configExcludedFields =\n translationService.getConfigExcludedFields(collectionSlug)\n const allExcludedPaths = [...excludedPaths, ...configExcludedFields]\n\n if (pluginOptions.debugging && allExcludedPaths.length > 0) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Excluded paths for ${targetLocale}: ${allExcludedPaths.join(', ')}`,\n )\n }\n\n // Get existing document in target locale to preserve excluded fields\n // Only needed if exclusions are enabled\n let existingDoc: any = null\n if (enableExclusions && allExcludedPaths.length > 0) {\n try {\n const existingResult = await req.payload.findByID({\n id: doc.id,\n collection: collectionSlug,\n fallbackLocale: false,\n locale: targetLocale,\n })\n existingDoc = existingResult\n } catch (error) {\n // Document doesn't exist in this locale yet, that's okay\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] No existing document for ${targetLocale}, will create new`,\n )\n }\n }\n }\n\n // Translate the document\n const translatedData = await translationService.translate({\n collection: collectionSlug,\n data: doc,\n excludedPaths: allExcludedPaths,\n fromLocale: defaultLocale,\n payload: req.payload,\n toLocale: targetLocale,\n })\n\n // Merge translated data with existing, preserving excluded fields\n const finalData = translatedData\n if (existingDoc && allExcludedPaths.length > 0) {\n // Preserve excluded fields from existing document\n for (const excludedPath of allExcludedPaths) {\n const existingValue = getNestedValue(existingDoc, excludedPath)\n if (existingValue !== undefined) {\n setNestedValue(finalData, excludedPath, existingValue)\n }\n }\n }\n\n // Update the document in the target locale\n await req.payload.update({\n id: doc.id,\n collection: collectionSlug,\n data: finalData,\n locale: targetLocale,\n // Prevent infinite loop - don't trigger hooks\n context: {\n skipAutoTranslate: true,\n },\n req,\n })\n\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Successfully translated ${collectionSlug}:${doc.id} to ${targetLocale}`,\n )\n }\n } catch (error) {\n // Log detailed error information\n const errorMessage = error instanceof Error ? error.message : String(error)\n const errorStack = error instanceof Error ? error.stack : undefined\n const errorDetails = {\n collection: collectionSlug,\n documentId: doc.id,\n fromLocale: defaultLocale,\n message: errorMessage,\n stack: errorStack,\n toLocale: targetLocale,\n }\n\n req.payload.logger.error(\n `[Auto-Translate Plugin] Error translating ${collectionSlug}:${doc.id} to ${targetLocale}:`,\n )\n req.payload.logger.error(errorMessage)\n\n if (pluginOptions.debugging && errorStack) {\n req.payload.logger.error('Stack trace:')\n req.payload.logger.error(errorStack)\n }\n\n // Log additional context if it's an OpenAI error\n if (error && typeof error === 'object' && 'error' in error) {\n req.payload.logger.error('OpenAI error details:')\n req.payload.logger.error(JSON.stringify(error, null, 2))\n }\n\n // Continue with other locales even if one fails\n }\n }\n\n return result\n })\n\n // Prevent infinite loops - skip translation if triggered by our own update\n const originalAfterOperationHooks = [...(collection.hooks.afterOperation || [])]\n collection.hooks.afterOperation = [\n async (args) => {\n // Skip if this update was triggered by auto-translate\n // Context might not be available on all operations\n if ('req' in args && args.req?.context?.skipAutoTranslate) {\n return args.result\n }\n\n // Run all hooks including translation\n for (const hook of originalAfterOperationHooks) {\n const result = await hook(args)\n if (result !== undefined) {\n args.result = result\n }\n }\n\n return args.result\n },\n ]\n\n if (pluginOptions.debugging) {\n console.log(`[Auto-Translate Plugin] Configured collection: ${collectionSlug}`)\n }\n }\n }\n\n return config\n }\n\n/**\n * Helper function to get nested value from object using dot notation\n */\nfunction getNestedValue(obj: any, path: string): any {\n return path.split('.').reduce((current, part) => {\n if (current === null || current === undefined) {\n return undefined\n }\n return current[part]\n }, obj)\n}\n\n/**\n * Helper function to set nested value in object using dot notation\n */\nfunction setNestedValue(obj: any, path: string, value: any): void {\n const parts = path.split('.')\n let current = obj\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n if (!(part in current) || current[part] === null || typeof current[part] !== 'object') {\n // Check if next part is a number (array index)\n const nextPart = parts[i + 1]\n current[part] = /^\\d+$/.test(nextPart) ? [] : {}\n }\n current = current[part]\n }\n\n current[parts[parts.length - 1]] = value\n}\n"],"names":["getTranslationExclusionsCollection","getTranslationSettingsGlobal","TranslationService","injectTranslationControls","autoTranslate","pluginOptions","config","disabled","debugging","console","log","collections","localization","warn","localizationConfig","defaultLocale","allLocales","Array","isArray","locales","map","l","code","enableExclusions","Object","keys","exclusionsSlug","translationExclusionsSlug","push","globals","settingsSlug","translationSettingsSlug","translationService","collectionSlug","collectionConfig","enabled","collection","find","c","slug","fields","name","type","admin","description","position","defaultValue","enableTranslationSyncByDefault","label","autoInjectUI","hooks","afterOperation","operation","req","result","payload","logger","error","JSON","stringify","doc","locale","info","_status","translationSync","id","secondaryLocales","filter","targetLocale","excludedPaths","getExclusions","toString","configExcludedFields","getConfigExcludedFields","allExcludedPaths","length","join","existingDoc","existingResult","findByID","fallbackLocale","translatedData","translate","data","fromLocale","toLocale","finalData","excludedPath","existingValue","getNestedValue","undefined","setNestedValue","update","context","skipAutoTranslate","errorMessage","Error","message","String","errorStack","stack","errorDetails","documentId","originalAfterOperationHooks","args","hook","obj","path","split","reduce","current","part","value","parts","i","nextPart","test"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AutoTranslateConfig } from './types/index.js'\n\nimport { getTranslationExclusionsCollection } from './collections/translationExclusions.js'\nimport { getTranslationSettingsGlobal } from './globals/translationSettings.js'\nimport { TranslationService } from './services/translationService.js'\nimport { injectTranslationControls } from './utilities/injectTranslationControls.js'\n\nexport { getTranslationExclusionsCollection } from './collections/translationExclusions.js'\nexport { getTranslationSettingsGlobal } from './globals/translationSettings.js'\nexport { TranslationService } from './services/translationService.js'\nexport * from './types/index.js'\n\n// Fields that must never be passed as data to payload.update / payload.create\n// (Postgres/drizzle rejects them; MongoDB silently ignores them)\nconst SYSTEM_FIELDS = new Set([\n 'id',\n 'createdAt',\n 'updatedAt',\n '_status',\n '__v',\n 'globalType',\n 'updatedBy',\n])\n\nfunction stripSystemFields(data: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(data)) {\n if (!SYSTEM_FIELDS.has(key)) {\n result[key] = value\n }\n }\n return result\n}\n\n/**\n * Strips `id` from objects that are direct elements of arrays, recursively\n * through the data tree. This prevents Postgres unique-constraint violations\n * when inserting locale-specific rows into array tables (e.g. posts_content)\n * that share a single PRIMARY KEY on `id` across all locales.\n *\n * Relationship objects (plain objects that are NOT direct array items) keep\n * their `id` so that Payload can still resolve them correctly.\n */\nfunction stripArrayItemIds(data: unknown): unknown {\n if (Array.isArray(data)) {\n return data.map((item) => {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n // Direct array item — strip its Payload-internal `id`\n const { id: _id, ...rest } = item as Record<string, unknown>\n const processed: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(rest)) {\n processed[key] = stripArrayItemIds(value)\n }\n return processed\n }\n return stripArrayItemIds(item)\n })\n }\n\n if (data && typeof data === 'object') {\n const result: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(data as Record<string, unknown>)) {\n result[key] = stripArrayItemIds(value)\n }\n return result\n }\n\n return data\n}\n\nexport const autoTranslate =\n (pluginOptions: AutoTranslateConfig) =>\n (incomingConfig: Config): Config => {\n // Create a shallow copy so we never mutate the caller's config object\n const config: Config = { ...incomingConfig }\n\n // If the plugin is disabled, return config immediately without any modifications\n if (pluginOptions.disabled) {\n if (pluginOptions.debugging) {\n console.log('[Auto-Translate Plugin] Plugin is disabled, skipping all modifications')\n }\n return config\n }\n\n if (!config.localization) {\n console.warn(\n '[Auto-Translate Plugin] No localization config found. Plugin will not function properly.',\n )\n return config\n }\n\n const localizationConfig = config.localization\n const defaultLocale = localizationConfig.defaultLocale\n const allLocales = Array.isArray(localizationConfig.locales)\n ? localizationConfig.locales.map((l) => (typeof l === 'string' ? l : l.code))\n : []\n\n // Default enableExclusions to true for backward compatibility\n const enableExclusions = pluginOptions.enableExclusions !== false\n\n if (pluginOptions.debugging) {\n console.log('[Auto-Translate Plugin] Configuration:')\n console.log('- Default locale:', defaultLocale)\n console.log('- All locales:', allLocales)\n console.log('- Enabled collections:', Object.keys(pluginOptions.collections || {}))\n console.log('- Exclusions enabled:', enableExclusions)\n }\n\n // Add translation exclusions collection (only if exclusions are enabled)\n // Use spread to avoid mutating the original array\n if (enableExclusions) {\n const exclusionsSlug = pluginOptions.translationExclusionsSlug || 'translation-exclusions'\n config.collections = [\n ...(config.collections || []),\n getTranslationExclusionsCollection(exclusionsSlug),\n ]\n } else {\n config.collections = [...(config.collections || [])]\n }\n\n // Add translation settings global using spread\n const settingsSlug = pluginOptions.translationSettingsSlug || 'translation-settings'\n config.globals = [...(config.globals || []), getTranslationSettingsGlobal(settingsSlug)]\n\n // Initialize translation service\n const translationService = new TranslationService(pluginOptions)\n\n // Configure collections with auto-translate\n if (pluginOptions.collections) {\n for (const rawSlug in pluginOptions.collections) {\n // Payload 3.85+ requires CollectionSlug (strict union), but for...in\n // yields string. Cast once here and use collectionSlug throughout.\n const collectionSlug = rawSlug as import('payload').CollectionSlug\n const collectionConfig =\n pluginOptions.collections[collectionSlug as keyof typeof pluginOptions.collections]\n\n // Skip if disabled\n if (\n collectionConfig === false ||\n (typeof collectionConfig === 'object' && collectionConfig.enabled === false)\n ) {\n continue\n }\n\n const collection = config.collections.find((c) => c.slug === collectionSlug)\n\n if (!collection) {\n console.warn(`[Auto-Translate Plugin] Collection \"${collectionSlug}\" not found in config`)\n continue\n }\n\n // Add translationSync field to collection\n collection.fields = [\n ...collection.fields,\n {\n name: 'translationSync',\n type: 'checkbox',\n admin: {\n description:\n 'When enabled, changes in the default language will automatically translate to other languages',\n position: 'sidebar',\n },\n defaultValue: pluginOptions.enableTranslationSyncByDefault ?? true,\n label: 'Enable Auto-Translation',\n },\n ]\n\n // Auto-inject TranslationControl component into all localized fields\n // Only inject if exclusions are enabled (otherwise there's nothing to control)\n if (enableExclusions && pluginOptions.autoInjectUI !== false) {\n collection.fields = injectTranslationControls(collection.fields, defaultLocale)\n\n if (pluginOptions.debugging) {\n console.log(`[Auto-Translate Plugin] Auto-injected UI controls for: ${collectionSlug}`)\n }\n }\n\n // Add hooks for translation\n if (!collection.hooks) {\n collection.hooks = {}\n }\n\n if (!collection.hooks.afterOperation) {\n collection.hooks.afterOperation = []\n }\n\n // Main translation hook\n const translationHook = async ({ operation, req, result }: any) => {\n // Only process create and updateByID operations\n if (operation !== 'create' && operation !== 'updateByID') {\n if (pluginOptions.debugging) {\n req.payload.logger.error(\n `[Auto-Translate Plugin] Skipping translation - not create or update operation: ${operation}`,\n )\n }\n return result\n }\n\n // For create/update operations, result should have an id property\n if (!result || typeof result !== 'object' || !('id' in result)) {\n if (pluginOptions.debugging) {\n req.payload.logger.error(\n `[Auto-Translate Plugin] No document found in result: ${JSON.stringify(result)}`,\n )\n }\n return result\n }\n\n const doc = result\n\n // Only translate if editing from default locale\n if (req.locale !== defaultLocale) {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping translation - not default locale (current: ${req.locale}, default: ${defaultLocale})`,\n )\n }\n return result\n }\n\n // Skip translation for drafts when autosave is enabled\n // Only translate when document is published\n if (doc._status && doc._status !== 'published') {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping translation - document is a draft (status: ${doc._status})`,\n )\n }\n return result\n }\n\n // Check if translation sync is enabled\n if (!doc.translationSync) {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping translation - translationSync disabled for ${collectionSlug}:${doc.id}`,\n )\n }\n return result\n }\n\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Processing ${collectionSlug} document ${operation}: ${doc.id}`,\n )\n }\n\n // Get secondary locales (all locales except default)\n const secondaryLocales = allLocales.filter((locale) => locale !== defaultLocale)\n\n // Translate to each secondary locale\n for (const targetLocale of secondaryLocales) {\n try {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Translating ${collectionSlug}:${doc.id} from ${defaultLocale} to ${targetLocale}`,\n )\n }\n\n // Get field-level exclusions for this locale (only if exclusions are enabled)\n let excludedPaths: string[] = []\n if (enableExclusions) {\n excludedPaths = await translationService.getExclusions(\n req.payload,\n collectionSlug,\n doc.id.toString(),\n targetLocale,\n )\n }\n\n // Get global/collection-level excluded fields\n const configExcludedFields =\n translationService.getConfigExcludedFields(collectionSlug)\n const allExcludedPaths = [...excludedPaths, ...configExcludedFields]\n\n if (pluginOptions.debugging && allExcludedPaths.length > 0) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Excluded paths for ${targetLocale}: ${allExcludedPaths.join(', ')}`,\n )\n }\n\n // Get existing document in target locale to preserve excluded fields\n // Only needed if exclusions are enabled\n let existingDoc: any = null\n if (enableExclusions && allExcludedPaths.length > 0) {\n try {\n const existingResult = await req.payload.findByID({\n id: doc.id,\n collection: collectionSlug,\n fallbackLocale: false,\n locale: targetLocale,\n })\n existingDoc = existingResult\n } catch (error) {\n // Document doesn't exist in this locale yet, that's okay\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] No existing document for ${targetLocale}, will create new`,\n )\n }\n }\n }\n\n // Translate the document\n const translatedData = await translationService.translate({\n collection: collectionSlug,\n data: doc,\n excludedPaths: allExcludedPaths,\n fromLocale: defaultLocale,\n payload: req.payload,\n toLocale: targetLocale,\n })\n\n // Merge translated data with existing, preserving excluded fields\n let finalData = { ...translatedData }\n if (existingDoc && allExcludedPaths.length > 0) {\n // Preserve excluded fields from existing document\n for (const excludedPath of allExcludedPaths) {\n const existingValue = getNestedValue(existingDoc, excludedPath)\n if (existingValue !== undefined) {\n setNestedValue(finalData, excludedPath, existingValue)\n }\n }\n }\n\n // Strip system/internal fields before updating so Postgres adapter\n // does not receive `id`, `createdAt`, `updatedAt`, etc. as data fields.\n // MongoDB is lenient with extra fields; Postgres/drizzle raises\n // ValidationError: The following field is invalid: id\n //\n // Also strip `id` from nested array items: Payload's array tables\n // (e.g. posts_content) have a shared PRIMARY KEY on `id` across all\n // locales, so reusing source-locale item IDs for a target locale causes\n // a Postgres 23505 unique-constraint violation.\n const strippedArrayIds = stripArrayItemIds(finalData)\n const updateData = stripSystemFields(strippedArrayIds as Record<string, unknown>)\n\n // Update the document in the target locale\n await req.payload.update({\n id: doc.id,\n collection: collectionSlug,\n data: updateData,\n locale: targetLocale,\n // Prevent infinite loop - don't trigger hooks\n context: {\n skipAutoTranslate: true,\n },\n req,\n })\n\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Successfully translated ${collectionSlug}:${doc.id} to ${targetLocale}`,\n )\n }\n } catch (error) {\n // Log detailed error information\n const errorMessage = error instanceof Error ? error.message : String(error)\n const errorStack = error instanceof Error ? error.stack : undefined\n\n req.payload.logger.error(\n `[Auto-Translate Plugin] Error translating ${collectionSlug}:${doc.id} to ${targetLocale}:`,\n )\n req.payload.logger.error(errorMessage)\n\n if (pluginOptions.debugging && errorStack) {\n req.payload.logger.error('Stack trace:')\n req.payload.logger.error(errorStack)\n }\n\n // Log additional context if it's an OpenAI error\n if (error && typeof error === 'object' && 'error' in error) {\n req.payload.logger.error('OpenAI error details:')\n req.payload.logger.error(JSON.stringify(error, null, 2))\n }\n\n // Continue with other locales even if one fails\n }\n }\n\n return result\n }\n\n // Prevent infinite loops - skip translation if triggered by our own update\n // Wrap ALL afterOperation hooks so the skipAutoTranslate context is checked first\n const existingHooks = [...(collection.hooks.afterOperation || []), translationHook]\n collection.hooks.afterOperation = [\n async (args: any) => {\n // Skip if this update was triggered by auto-translate\n if ('req' in args && args.req?.context?.skipAutoTranslate) {\n return args.result\n }\n\n // Run all hooks including translation\n for (const hook of existingHooks) {\n const hookResult = await hook(args)\n if (hookResult !== undefined) {\n args.result = hookResult\n }\n }\n\n return args.result\n },\n ]\n\n if (pluginOptions.debugging) {\n console.log(`[Auto-Translate Plugin] Configured collection: ${collectionSlug}`)\n }\n }\n }\n\n return config\n }\n\n/**\n * Helper function to get nested value from object using dot notation\n */\nfunction getNestedValue(obj: any, path: string): any {\n return path.split('.').reduce((current, part) => {\n if (current === null || current === undefined) {\n return undefined\n }\n return current[part]\n }, obj)\n}\n\n/**\n * Helper function to set nested value in object using dot notation\n */\nfunction setNestedValue(obj: any, path: string, value: any): void {\n const parts = path.split('.')\n let current = obj\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n if (!(part in current) || current[part] === null || typeof current[part] !== 'object') {\n // Check if next part is a number (array index)\n const nextPart = parts[i + 1]\n current[part] = /^\\d+$/.test(nextPart) ? [] : {}\n }\n current = current[part]\n }\n\n current[parts[parts.length - 1]] = value\n}\n"],"names":["getTranslationExclusionsCollection","getTranslationSettingsGlobal","TranslationService","injectTranslationControls","SYSTEM_FIELDS","Set","stripSystemFields","data","result","key","value","Object","entries","has","stripArrayItemIds","Array","isArray","map","item","id","_id","rest","processed","autoTranslate","pluginOptions","incomingConfig","config","disabled","debugging","console","log","localization","warn","localizationConfig","defaultLocale","allLocales","locales","l","code","enableExclusions","keys","collections","exclusionsSlug","translationExclusionsSlug","settingsSlug","translationSettingsSlug","globals","translationService","rawSlug","collectionSlug","collectionConfig","enabled","collection","find","c","slug","fields","name","type","admin","description","position","defaultValue","enableTranslationSyncByDefault","label","autoInjectUI","hooks","afterOperation","translationHook","operation","req","payload","logger","error","JSON","stringify","doc","locale","info","_status","translationSync","secondaryLocales","filter","targetLocale","excludedPaths","getExclusions","toString","configExcludedFields","getConfigExcludedFields","allExcludedPaths","length","join","existingDoc","existingResult","findByID","fallbackLocale","translatedData","translate","fromLocale","toLocale","finalData","excludedPath","existingValue","getNestedValue","undefined","setNestedValue","strippedArrayIds","updateData","update","context","skipAutoTranslate","errorMessage","Error","message","String","errorStack","stack","existingHooks","args","hook","hookResult","obj","path","split","reduce","current","part","parts","i","nextPart","test"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/translationService.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\nimport OpenAI from 'openai'\n\nimport type { AutoTranslateConfig, TranslateOptions } from '../types/index.js'\n\nimport { filterExcludedPaths } from '../utilities/fieldHelpers.js'\n\nexport class TranslationService {\n private client?: OpenAI\n private config: AutoTranslateConfig\n\n constructor(config: AutoTranslateConfig) {\n this.config = config\n }\n\n /**\n * Extracts translatable text from lexical editor nodes\n */\n private extractFromLexicalNode(\n node: any,\n path: string,\n strings: Map<string, string>,\n deduplicationMap: Map<string, string[]>,\n ): any {\n const enableDeduplication = this.config.enableDeduplication !== false // Default to true\n\n // Handle text nodes - skip whitespace-only or very short text\n if (node.type === 'text' && node.text && typeof node.text === 'string') {\n const trimmed = node.text.trim()\n\n // Skip if empty, whitespace-only, or too short\n if (trimmed.length === 0 || this.shouldSkipString(node.text, `${path}.text`)) {\n return node\n }\n\n const textPath = `${path}.text`\n\n if (enableDeduplication) {\n // Check for deduplication\n if (deduplicationMap.has(trimmed)) {\n // This string already exists, just store the path mapping\n const existingPaths = deduplicationMap.get(trimmed)!\n existingPaths.push(textPath)\n return { ...node, text: `__TRANSLATE_${textPath}__` }\n } else {\n // New unique string\n strings.set(textPath, node.text)\n deduplicationMap.set(trimmed, [textPath])\n return { ...node, text: `__TRANSLATE_${textPath}__` }\n }\n } else {\n // No deduplication - add every string\n strings.set(textPath, node.text)\n deduplicationMap.set(trimmed, [textPath])\n return { ...node, text: `__TRANSLATE_${textPath}__` }\n }\n }\n\n // Handle nodes with children\n if (node.children && Array.isArray(node.children)) {\n return {\n ...node,\n children: node.children.map((child: any, index: number) =>\n this.extractFromLexicalNode(\n child,\n `${path}.children[${index}]`,\n strings,\n deduplicationMap,\n ),\n ),\n }\n }\n\n return node\n }\n\n /**\n * Extracts translatable strings from data structure\n * Returns a map of paths to translatable values and metadata for reconstruction\n */\n private extractTranslatableStrings(\n data: any,\n path: string = '',\n ): { deduplicationMap: Map<string, string[]>; metadata: any; strings: Map<string, string> } {\n const strings = new Map<string, string>()\n const deduplicationMap = new Map<string, string[]>() // value -> [paths]\n const enableDeduplication = this.config.enableDeduplication !== false // Default to true\n\n const extract = (obj: any, currentPath: string): any => {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n // Handle lexical editor format\n if (this.isLexicalEditorNode(obj)) {\n return this.extractFromLexicalNode(obj, currentPath, strings, deduplicationMap)\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return obj.map((item, index) => extract(item, `${currentPath}[${index}]`))\n }\n\n // Handle objects\n if (typeof obj === 'object') {\n const result: any = {}\n for (const [key, value] of Object.entries(obj)) {\n const newPath = currentPath ? `${currentPath}.${key}` : key\n result[key] = extract(value, newPath)\n }\n return result\n }\n\n // Handle strings\n if (typeof obj === 'string' && obj.trim().length > 0) {\n // Skip IDs and other non-translatable strings\n if (!this.shouldSkipString(obj, currentPath)) {\n if (enableDeduplication) {\n // Check for deduplication\n const trimmedValue = obj.trim()\n if (deduplicationMap.has(trimmedValue)) {\n // This string already exists, just store the path mapping\n const existingPaths = deduplicationMap.get(trimmedValue)!\n existingPaths.push(currentPath)\n return `__TRANSLATE_${currentPath}__`\n } else {\n // New unique string\n strings.set(currentPath, obj)\n deduplicationMap.set(trimmedValue, [currentPath])\n return `__TRANSLATE_${currentPath}__`\n }\n } else {\n // No deduplication - add every string\n strings.set(currentPath, obj)\n deduplicationMap.set(obj.trim(), [currentPath])\n return `__TRANSLATE_${currentPath}__`\n }\n }\n }\n\n return obj\n }\n\n const metadata = extract(data, path)\n return { deduplicationMap, metadata, strings }\n }\n\n /**\n * Lazily initialize OpenAI client only when needed\n */\n private getOpenAIClient(): OpenAI {\n if (!this.client) {\n const apiKey = this.config.provider?.apiKey || process.env.OPENAI_API_KEY\n if (!apiKey) {\n throw new Error(\n 'OpenAI API key is required. Set OPENAI_API_KEY environment variable or provide it in plugin config.',\n )\n }\n\n this.client = new OpenAI({\n apiKey,\n baseURL: this.config.provider?.baseURL || process.env.OPENAI_BASE_URL,\n })\n }\n return this.client\n }\n\n /**\n * Gets the original value at a path in metadata (helper for deduplication)\n */\n private getOriginalValue(metadata: any, path: string): null | string {\n try {\n const parts = path.split(/[.[\\]]/).filter(Boolean)\n let current = metadata\n for (const part of parts) {\n if (current === null || current === undefined) {\n return null\n }\n current = current[part]\n }\n return typeof current === 'string' ? current : null\n } catch {\n return null\n }\n }\n\n /**\n * Gets translation settings from the global or returns defaults\n */\n private async getTranslationSettings(payload: Payload): Promise<{\n maxTokens?: number\n model: string\n systemPrompt: string\n temperature: number\n translationRules: string\n }> {\n const settingsSlug = this.config.translationSettingsSlug || 'translation-settings'\n\n // Default values\n const defaults = {\n maxTokens: undefined,\n model: this.config.provider?.model || 'gpt-4o',\n systemPrompt:\n 'You are a professional translator. Translate the JSON object values from {fromLocale} to {toLocale}.',\n temperature: 0.3,\n translationRules: `Rules:\n - Only translate the values, never the keys\n - Preserve the exact JSON structure\n - Maintain formatting, HTML tags, and special characters\n - Return only valid JSON without any markdown formatting or code blocks\n - If a value is already in the target language or is a proper noun, keep it as is`,\n }\n\n try {\n const settings = await payload.findGlobal({\n slug: settingsSlug,\n })\n\n if (settings) {\n return {\n maxTokens: settings.maxTokens || defaults.maxTokens,\n model: settings.model || defaults.model,\n systemPrompt: settings.systemPrompt || defaults.systemPrompt,\n temperature:\n typeof settings.temperature === 'number' ? settings.temperature : defaults.temperature,\n translationRules: settings.translationRules || defaults.translationRules,\n }\n }\n } catch (error) {\n if (this.config.debugging) {\n console.warn(\n '[Auto-Translate] Could not fetch translation settings, using defaults:',\n error,\n )\n }\n }\n\n return defaults\n }\n\n /**\n * Checks if an object is a lexical editor node\n */\n private isLexicalEditorNode(obj: any): boolean {\n return (\n obj &&\n typeof obj === 'object' &&\n 'type' in obj &&\n 'version' in obj &&\n ('children' in obj || 'text' in obj)\n )\n }\n\n /**\n * Reconstructs data with translated strings, applying deduplicated translations\n */\n private reconstructWithTranslations(\n metadata: any,\n translations: Map<string, string>,\n deduplicationMap: Map<string, string[]>,\n ): any {\n // Build a comprehensive translation map including deduplicated paths\n const fullTranslations = new Map<string, string>()\n\n // For each unique string that was translated\n translations.forEach((translatedValue, originalPath) => {\n fullTranslations.set(originalPath, translatedValue)\n\n // Find all paths that had the same original value\n const originalValue = this.getOriginalValue(metadata, originalPath)\n if (originalValue) {\n const trimmed = originalValue.replace(/^__TRANSLATE_(.+)__$/, '$1')\n // Look through deduplication map to find all paths with same value\n for (const [value, paths] of deduplicationMap.entries()) {\n if (paths.includes(originalPath)) {\n // Apply the same translation to all paths with this value\n paths.forEach((path) => {\n fullTranslations.set(path, translatedValue)\n })\n break\n }\n }\n }\n })\n\n const reconstruct = (obj: any): any => {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return obj.map((item) => reconstruct(item))\n }\n\n // Handle objects\n if (typeof obj === 'object') {\n const result: any = {}\n for (const [key, value] of Object.entries(obj)) {\n result[key] = reconstruct(value)\n }\n return result\n }\n\n // Replace translation placeholders\n if (typeof obj === 'string' && obj.startsWith('__TRANSLATE_')) {\n const path = obj.slice(12, -2) // Remove __TRANSLATE_ prefix and __ suffix\n return fullTranslations.get(path) || obj\n }\n\n return obj\n }\n\n return reconstruct(metadata)\n }\n\n /**\n * Determines if a string should be skipped from translation\n */\n private shouldSkipString(str: string, path: string): boolean {\n // Skip IDs (MongoDB ObjectIds and similar)\n if (/^[a-f0-9]{24}$/i.test(str)) {\n return true\n }\n\n // Skip URLs\n if (/^https?:\\/\\//.test(str)) {\n return true\n }\n\n // Skip file paths\n if (/^\\/\\S*\\.(jpg|jpeg|png|gif|webp|svg|pdf|mp4|webm|ogg|mp3|wav)$/i.test(str)) {\n return true\n }\n\n // Skip email addresses\n if (/^[^\\s@]+@[^\\s@][^\\s.@]*\\.[^\\s@]+$/.test(str)) {\n return true\n }\n\n // Skip ISO date strings\n if (/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(str)) {\n return true\n }\n\n // Skip date-time strings like \"2019-01-31 12:05:04\"\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(str)) {\n return true\n }\n\n // Skip percentages like \"100%\"\n if (/^\\d+%$/.test(str)) {\n return true\n }\n\n // Skip pure numbers\n if (/^\\d+$/.test(str)) {\n return true\n }\n\n // Skip whitespace-only strings (including single spaces)\n if (str.trim().length === 0) {\n return true\n }\n\n // Skip very short strings based on config (default: 3 characters)\n const minLength = this.config.minStringLength !== undefined ? this.config.minStringLength : 3\n if (str.trim().length < minLength) {\n return true\n }\n\n // Skip status values\n if (['archived', 'draft', 'pending', 'published'].includes(str.toLowerCase())) {\n return true\n }\n\n // Skip paths ending with id, createdAt, updatedAt, etc.\n const pathLower = path.toLowerCase()\n if (\n pathLower.endsWith('id') ||\n pathLower.endsWith('_id') ||\n pathLower.includes('createdat') ||\n pathLower.includes('updatedat')\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * Legacy translation method (sends entire structure)\n */\n private async translateWithOpenAILegacy(\n data: any,\n fromLocale: string,\n toLocale: string,\n payload: Payload,\n ): Promise<any> {\n const client = this.getOpenAIClient()\n const timeout = this.config.provider?.timeout || 30000\n\n try {\n // Get translation settings from global\n const settings = await this.getTranslationSettings(payload)\n\n // Build system message from settings\n const systemPrompt = settings.systemPrompt\n .replace('{fromLocale}', fromLocale)\n .replace('{toLocale}', toLocale)\n\n const systemMessage = `${systemPrompt}\\n\\n${settings.translationRules}`\n\n const requestParams: any = {\n messages: [\n {\n content: systemMessage,\n role: 'system',\n },\n {\n content: JSON.stringify(data, null, 2),\n role: 'user',\n },\n ],\n model: settings.model,\n response_format: { type: 'json_object' },\n temperature: settings.temperature,\n }\n\n // Add maxTokens if specified\n if (settings.maxTokens) {\n requestParams.max_tokens = settings.maxTokens\n }\n\n const response = await client.chat.completions.create(requestParams, { timeout })\n\n const translatedText = response.choices[0]?.message?.content\n\n if (!translatedText) {\n throw new Error('No translation received from OpenAI')\n }\n\n return JSON.parse(translatedText)\n } catch (error) {\n console.error('[Auto-Translate] Translation error:', error)\n throw error\n }\n }\n\n /**\n * Gets global and collection-specific excluded fields\n */\n getConfigExcludedFields(collection: string): string[] {\n const globalExclusions = this.config.excludeFields || []\n const collectionConfig = this.config.collections?.[collection]\n\n if (typeof collectionConfig === 'object' && collectionConfig.excludeFields) {\n return [...globalExclusions, ...collectionConfig.excludeFields]\n }\n\n return globalExclusions\n }\n\n /**\n * Gets translation exclusions for a document\n */\n async getExclusions(\n payload: Payload,\n collection: string,\n documentId: string,\n locale: string,\n ): Promise<string[]> {\n const exclusionsSlug = this.config.translationExclusionsSlug || 'translation-exclusions'\n\n try {\n const result = await payload.find({\n collection: exclusionsSlug,\n limit: 1,\n where: {\n and: [\n { collectionSlug: { equals: collection } },\n { documentId: { equals: documentId } },\n { locale: { equals: locale } },\n ],\n },\n })\n\n if (result.docs.length > 0) {\n const exclusion = result.docs[0] as any\n return exclusion.excludedPaths?.map((item: any) => item.path) || []\n }\n\n return []\n } catch (error) {\n if (this.config.debugging) {\n payload.logger.error(`[Auto-Translate] Error fetching exclusions: ${error}`)\n }\n return []\n }\n }\n\n /**\n * Main translation method\n */\n async translate(options: TranslateOptions): Promise<any> {\n const { collection, data, excludedPaths = [], fromLocale, payload, toLocale } = options\n\n // Filter out excluded paths before translation\n const dataToTranslate = filterExcludedPaths(data, excludedPaths)\n\n if (this.config.debugging) {\n payload.logger.info(\n `[Auto-Translate] Translating from ${fromLocale} to ${toLocale} for collection ${collection}`,\n )\n payload.logger.info(`[Auto-Translate] Excluded paths: ${excludedPaths.join(', ')}`)\n }\n\n // Use custom translator if provided\n if (this.config.provider?.customTranslate) {\n return await this.config.provider.customTranslate(options)\n }\n\n // Use OpenAI by default\n return await this.translateWithOpenAI(dataToTranslate, fromLocale, toLocale, payload)\n }\n\n /**\n * Translates using OpenAI API (optimized version)\n * This method is now public and can be used directly in your application\n */\n async translateWithOpenAI(\n data: any,\n fromLocale: string,\n toLocale: string,\n payload: Payload,\n ): Promise<any> {\n const client = this.getOpenAIClient()\n\n // Use optimization by default (can be disabled via config)\n const useOptimization = this.config.optimizeTranslation !== false\n\n if (!useOptimization) {\n // Use legacy approach: send entire structure\n return this.translateWithOpenAILegacy(data, fromLocale, toLocale, payload)\n }\n\n // Extract only translatable strings with deduplication\n const { deduplicationMap, metadata, strings } = this.extractTranslatableStrings(data)\n\n if (strings.size === 0) {\n // Nothing to translate\n return data\n }\n\n // Create a simple object with just the strings to translate\n const stringsToTranslate: Record<string, string> = {}\n strings.forEach((value, key) => {\n stringsToTranslate[key] = value\n })\n\n if (this.config.debugging) {\n const originalSize = JSON.stringify(data).length\n const optimizedSize = JSON.stringify(stringsToTranslate).length\n const reduction = ((1 - optimizedSize / originalSize) * 100).toFixed(1)\n\n // Calculate deduplication stats\n let totalPaths = 0\n deduplicationMap.forEach((paths) => {\n totalPaths += paths.length\n })\n const deduplicationSavings = totalPaths - strings.size\n const deduplicationPercent =\n totalPaths > 0 ? ((deduplicationSavings / totalPaths) * 100).toFixed(1) : '0'\n\n console.log('[Auto-Translate] ✨ Optimization Stats:')\n console.log(` 📊 Unique strings to translate: ${strings.size}`)\n console.log(` 🔄 Total string instances: ${totalPaths}`)\n console.log(\n ` 💾 Deduplication savings: ${deduplicationSavings} strings (${deduplicationPercent}%)`,\n )\n console.log(` 📦 Original JSON size: ${originalSize.toLocaleString()} bytes`)\n console.log(` 📦 Optimized JSON size: ${optimizedSize.toLocaleString()} bytes`)\n console.log(` 🎯 Total size reduction: ${reduction}%`)\n }\n\n try {\n // Get translation settings from global\n const settings = await this.getTranslationSettings(payload)\n\n // Add timeout configuration (default 30 seconds, configurable via plugin options)\n const timeout = this.config.provider?.timeout || 30000\n\n if (this.config.debugging) {\n console.log(\n `[Auto-Translate] Calling OpenAI API (timeout: ${timeout}ms, model: ${settings.model})`,\n )\n console.log(\n `[Auto-Translate] Payload size: ${JSON.stringify(stringsToTranslate).length} bytes`,\n )\n }\n\n // Build system message from settings\n const systemPrompt = settings.systemPrompt\n .replace('{fromLocale}', fromLocale)\n .replace('{toLocale}', toLocale)\n\n const systemMessage = `${systemPrompt}\\n\\n${settings.translationRules}`\n\n const requestParams: any = {\n messages: [\n {\n content: systemMessage,\n role: 'system',\n },\n {\n content: JSON.stringify(stringsToTranslate, null, 2),\n role: 'user',\n },\n ],\n model: settings.model,\n response_format: { type: 'json_object' },\n temperature: settings.temperature,\n }\n\n // Add maxTokens if specified\n if (settings.maxTokens) {\n requestParams.max_tokens = settings.maxTokens\n }\n\n const response = await client.chat.completions.create(requestParams, { timeout })\n\n const translatedText = response.choices[0]?.message?.content\n\n if (!translatedText) {\n throw new Error('No translation received from OpenAI')\n }\n\n if (this.config.debugging) {\n console.log(\n `[Auto-Translate] Received response from OpenAI (${translatedText.length} chars)`,\n )\n }\n\n let translatedStrings: any\n try {\n translatedStrings = JSON.parse(translatedText)\n } catch (parseError) {\n console.error('[Auto-Translate] Failed to parse OpenAI response as JSON')\n console.error('[Auto-Translate] Response text:', translatedText.substring(0, 500))\n throw new Error(\n `Invalid JSON response from OpenAI: ${parseError instanceof Error ? parseError.message : String(parseError)}`,\n )\n }\n\n // Convert back to Map\n const translationsMap = new Map<string, string>()\n for (const [key, value] of Object.entries(translatedStrings)) {\n if (typeof value === 'string') {\n translationsMap.set(key, value)\n }\n }\n\n // Reconstruct the full data structure with translations, applying deduplication\n return this.reconstructWithTranslations(metadata, translationsMap, deduplicationMap)\n } catch (error) {\n console.error('[Auto-Translate] Translation error:', error)\n\n // Provide more context about the error\n if (error && typeof error === 'object') {\n const err = error as any\n if (err.status) {\n console.error(`[Auto-Translate] OpenAI API status: ${err.status}`)\n }\n if (err.code) {\n console.error(`[Auto-Translate] Error code: ${err.code}`)\n }\n if (err.message) {\n console.error(`[Auto-Translate] Error message: ${err.message}`)\n }\n }\n\n // Add context to the error before re-throwing\n const contextualError = new Error(\n `Translation failed from ${fromLocale} to ${toLocale}: ${error instanceof Error ? error.message : String(error)}`,\n )\n contextualError.cause = error\n throw contextualError\n }\n }\n\n /**\n * Updates translation exclusions for a document\n */\n async updateExclusions(\n payload: Payload,\n collection: string,\n documentId: string,\n locale: string,\n excludedPaths: string[],\n ): Promise<void> {\n const exclusionsSlug = this.config.translationExclusionsSlug || 'translation-exclusions'\n\n try {\n const existing = await payload.find({\n collection: exclusionsSlug,\n limit: 1,\n where: {\n and: [\n { collectionSlug: { equals: collection } },\n { documentId: { equals: documentId } },\n { locale: { equals: locale } },\n ],\n },\n })\n\n const exclusionsData = {\n collectionSlug: collection,\n documentId,\n excludedPaths: excludedPaths.map((path) => ({ path })),\n locale,\n }\n\n if (existing.docs.length > 0) {\n await payload.update({\n id: existing.docs[0].id,\n collection: exclusionsSlug,\n data: exclusionsData,\n })\n } else {\n await payload.create({\n collection: exclusionsSlug,\n data: exclusionsData,\n })\n }\n\n if (this.config.debugging) {\n payload.logger.info(\n `[Auto-Translate] Updated exclusions for ${collection}:${documentId}:${locale}`,\n )\n }\n } catch (error) {\n if (this.config.debugging) {\n payload.logger.error(`[Auto-Translate] Error updating exclusions: ${error}`)\n }\n }\n }\n}\n"],"names":["OpenAI","filterExcludedPaths","TranslationService","client","config","extractFromLexicalNode","node","path","strings","deduplicationMap","enableDeduplication","type","text","trimmed","trim","length","shouldSkipString","textPath","has","existingPaths","get","push","set","children","Array","isArray","map","child","index","extractTranslatableStrings","data","Map","extract","obj","currentPath","undefined","isLexicalEditorNode","item","result","key","value","Object","entries","newPath","trimmedValue","metadata","getOpenAIClient","apiKey","provider","process","env","OPENAI_API_KEY","Error","baseURL","OPENAI_BASE_URL","getOriginalValue","parts","split","filter","Boolean","current","part","getTranslationSettings","payload","settingsSlug","translationSettingsSlug","defaults","maxTokens","model","systemPrompt","temperature","translationRules","settings","findGlobal","slug","error","debugging","console","warn","reconstructWithTranslations","translations","fullTranslations","forEach","translatedValue","originalPath","originalValue","replace","paths","includes","reconstruct","startsWith","slice","str","test","minLength","minStringLength","toLowerCase","pathLower","endsWith","translateWithOpenAILegacy","fromLocale","toLocale","timeout","systemMessage","requestParams","messages","content","role","JSON","stringify","response_format","max_tokens","response","chat","completions","create","translatedText","choices","message","parse","getConfigExcludedFields","collection","globalExclusions","excludeFields","collectionConfig","collections","getExclusions","documentId","locale","exclusionsSlug","translationExclusionsSlug","find","limit","where","and","collectionSlug","equals","docs","exclusion","excludedPaths","logger","translate","options","dataToTranslate","info","join","customTranslate","translateWithOpenAI","useOptimization","optimizeTranslation","size","stringsToTranslate","originalSize","optimizedSize","reduction","toFixed","totalPaths","deduplicationSavings","deduplicationPercent","log","toLocaleString","translatedStrings","parseError","substring","String","translationsMap","err","status","code","contextualError","cause","updateExclusions","existing","exclusionsData","update","id"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/services/translationService.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug, Payload } from 'payload'\n\nimport OpenAI from 'openai'\n\nimport type { AutoTranslateConfig, TranslateOptions } from '../types/index.js'\n\nimport { filterExcludedPaths } from '../utilities/fieldHelpers.js'\n\nexport class TranslationService {\n private client?: OpenAI\n private config: AutoTranslateConfig\n\n constructor(config: AutoTranslateConfig) {\n this.config = config\n }\n\n /**\n * Extracts translatable text from lexical editor nodes\n */\n private extractFromLexicalNode(\n node: any,\n path: string,\n strings: Map<string, string>,\n deduplicationMap: Map<string, string[]>,\n ): any {\n const enableDeduplication = this.config.enableDeduplication !== false // Default to true\n\n // Handle text nodes - skip whitespace-only or very short text\n if (node.type === 'text' && node.text && typeof node.text === 'string') {\n const trimmed = node.text.trim()\n\n // Skip if empty, whitespace-only, or too short\n if (trimmed.length === 0 || this.shouldSkipString(node.text, `${path}.text`)) {\n return node\n }\n\n const textPath = `${path}.text`\n\n if (enableDeduplication) {\n // Check for deduplication\n if (deduplicationMap.has(trimmed)) {\n // This string already exists, just store the path mapping\n const existingPaths = deduplicationMap.get(trimmed)!\n existingPaths.push(textPath)\n return { ...node, text: `__TRANSLATE_${textPath}__` }\n } else {\n // New unique string\n strings.set(textPath, node.text)\n deduplicationMap.set(trimmed, [textPath])\n return { ...node, text: `__TRANSLATE_${textPath}__` }\n }\n } else {\n // No deduplication - add every string\n strings.set(textPath, node.text)\n deduplicationMap.set(trimmed, [textPath])\n return { ...node, text: `__TRANSLATE_${textPath}__` }\n }\n }\n\n // Handle nodes with children\n if (node.children && Array.isArray(node.children)) {\n return {\n ...node,\n children: node.children.map((child: any, index: number) =>\n this.extractFromLexicalNode(\n child,\n `${path}.children[${index}]`,\n strings,\n deduplicationMap,\n ),\n ),\n }\n }\n\n return node\n }\n\n /**\n * Extracts translatable strings from data structure\n * Returns a map of paths to translatable values and metadata for reconstruction\n */\n private extractTranslatableStrings(\n data: any,\n path: string = '',\n ): { deduplicationMap: Map<string, string[]>; metadata: any; strings: Map<string, string> } {\n const strings = new Map<string, string>()\n const deduplicationMap = new Map<string, string[]>() // value -> [paths]\n const enableDeduplication = this.config.enableDeduplication !== false // Default to true\n\n const extract = (obj: any, currentPath: string): any => {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n // Handle lexical editor format\n if (this.isLexicalEditorNode(obj)) {\n return this.extractFromLexicalNode(obj, currentPath, strings, deduplicationMap)\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return obj.map((item, index) => extract(item, `${currentPath}[${index}]`))\n }\n\n // Handle objects\n if (typeof obj === 'object') {\n const result: any = {}\n for (const [key, value] of Object.entries(obj)) {\n const newPath = currentPath ? `${currentPath}.${key}` : key\n result[key] = extract(value, newPath)\n }\n return result\n }\n\n // Handle strings\n if (typeof obj === 'string' && obj.trim().length > 0) {\n // Skip IDs and other non-translatable strings\n if (!this.shouldSkipString(obj, currentPath)) {\n if (enableDeduplication) {\n // Check for deduplication\n const trimmedValue = obj.trim()\n if (deduplicationMap.has(trimmedValue)) {\n // This string already exists, just store the path mapping\n const existingPaths = deduplicationMap.get(trimmedValue)!\n existingPaths.push(currentPath)\n return `__TRANSLATE_${currentPath}__`\n } else {\n // New unique string\n strings.set(currentPath, obj)\n deduplicationMap.set(trimmedValue, [currentPath])\n return `__TRANSLATE_${currentPath}__`\n }\n } else {\n // No deduplication - add every string\n strings.set(currentPath, obj)\n deduplicationMap.set(obj.trim(), [currentPath])\n return `__TRANSLATE_${currentPath}__`\n }\n }\n }\n\n return obj\n }\n\n const metadata = extract(data, path)\n return { deduplicationMap, metadata, strings }\n }\n\n /**\n * Lazily initialize OpenAI client only when needed\n */\n private getOpenAIClient(): OpenAI {\n if (!this.client) {\n const apiKey = this.config.provider?.apiKey || process.env.OPENAI_API_KEY\n if (!apiKey) {\n throw new Error(\n 'OpenAI API key is required. Set OPENAI_API_KEY environment variable or provide it in plugin config.',\n )\n }\n\n this.client = new OpenAI({\n apiKey,\n baseURL: this.config.provider?.baseURL || process.env.OPENAI_BASE_URL,\n })\n }\n return this.client\n }\n\n /**\n * Gets the original value at a path in metadata (helper for deduplication)\n */\n private getOriginalValue(metadata: any, path: string): null | string {\n try {\n const parts = path.split(/[.[\\]]/).filter(Boolean)\n let current = metadata\n for (const part of parts) {\n if (current === null || current === undefined) {\n return null\n }\n current = current[part]\n }\n return typeof current === 'string' ? current : null\n } catch {\n return null\n }\n }\n\n /**\n * Gets translation settings from the global or returns defaults\n */\n private async getTranslationSettings(payload: Payload): Promise<{\n maxTokens?: number\n model: string\n systemPrompt: string\n temperature: number\n translationRules: string\n }> {\n const settingsSlug = this.config.translationSettingsSlug || 'translation-settings'\n\n // Default values\n const defaults = {\n maxTokens: undefined,\n model: this.config.provider?.model || 'gpt-4o',\n systemPrompt:\n 'You are a professional translator. Translate the JSON object values from {fromLocale} to {toLocale}.',\n temperature: 0.3,\n translationRules: `Rules:\n - Only translate the values, never the keys\n - Preserve the exact JSON structure\n - Maintain formatting, HTML tags, and special characters\n - Return only valid JSON without any markdown formatting or code blocks\n - If a value is already in the target language or is a proper noun, keep it as is`,\n }\n\n try {\n const settings = await payload.findGlobal({\n slug: settingsSlug as GlobalSlug,\n })\n\n if (settings) {\n return {\n maxTokens: settings.maxTokens || defaults.maxTokens,\n model: settings.model || defaults.model,\n systemPrompt: settings.systemPrompt || defaults.systemPrompt,\n temperature:\n typeof settings.temperature === 'number' ? settings.temperature : defaults.temperature,\n translationRules: settings.translationRules || defaults.translationRules,\n }\n }\n } catch (error) {\n if (this.config.debugging) {\n console.warn(\n '[Auto-Translate] Could not fetch translation settings, using defaults:',\n error,\n )\n }\n }\n\n return defaults\n }\n\n /**\n * Checks if an object is a lexical editor node\n */\n private isLexicalEditorNode(obj: any): boolean {\n return (\n obj &&\n typeof obj === 'object' &&\n 'type' in obj &&\n 'version' in obj &&\n ('children' in obj || 'text' in obj)\n )\n }\n\n /**\n * Reconstructs data with translated strings, applying deduplicated translations\n */\n private reconstructWithTranslations(\n metadata: any,\n translations: Map<string, string>,\n deduplicationMap: Map<string, string[]>,\n ): any {\n // Build a comprehensive translation map including deduplicated paths\n const fullTranslations = new Map<string, string>()\n\n // For each unique string that was translated\n translations.forEach((translatedValue, originalPath) => {\n fullTranslations.set(originalPath, translatedValue)\n\n // Find all paths that had the same original value\n const originalValue = this.getOriginalValue(metadata, originalPath)\n if (originalValue) {\n const trimmed = originalValue.replace(/^__TRANSLATE_(.+)__$/, '$1')\n // Look through deduplication map to find all paths with same value\n for (const [value, paths] of deduplicationMap.entries()) {\n if (paths.includes(originalPath)) {\n // Apply the same translation to all paths with this value\n paths.forEach((path) => {\n fullTranslations.set(path, translatedValue)\n })\n break\n }\n }\n }\n })\n\n const reconstruct = (obj: any): any => {\n if (obj === null || obj === undefined) {\n return obj\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return obj.map((item) => reconstruct(item))\n }\n\n // Handle objects\n if (typeof obj === 'object') {\n const result: any = {}\n for (const [key, value] of Object.entries(obj)) {\n result[key] = reconstruct(value)\n }\n return result\n }\n\n // Replace translation placeholders\n if (typeof obj === 'string' && obj.startsWith('__TRANSLATE_')) {\n const path = obj.slice(12, -2) // Remove __TRANSLATE_ prefix and __ suffix\n return fullTranslations.get(path) || obj\n }\n\n return obj\n }\n\n return reconstruct(metadata)\n }\n\n /**\n * Determines if a string should be skipped from translation\n */\n private shouldSkipString(str: string, path: string): boolean {\n // Skip IDs (MongoDB ObjectIds and similar)\n if (/^[a-f0-9]{24}$/i.test(str)) {\n return true\n }\n\n // Skip URLs\n if (/^https?:\\/\\//.test(str)) {\n return true\n }\n\n // Skip file paths\n if (/^\\/\\S*\\.(jpg|jpeg|png|gif|webp|svg|pdf|mp4|webm|ogg|mp3|wav)$/i.test(str)) {\n return true\n }\n\n // Skip email addresses\n if (/^[^\\s@]+@[^\\s@][^\\s.@]*\\.[^\\s@]+$/.test(str)) {\n return true\n }\n\n // Skip ISO date strings\n if (/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}/.test(str)) {\n return true\n }\n\n // Skip date-time strings like \"2019-01-31 12:05:04\"\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(str)) {\n return true\n }\n\n // Skip percentages like \"100%\"\n if (/^\\d+%$/.test(str)) {\n return true\n }\n\n // Skip pure numbers\n if (/^\\d+$/.test(str)) {\n return true\n }\n\n // Skip whitespace-only strings (including single spaces)\n if (str.trim().length === 0) {\n return true\n }\n\n // Skip very short strings based on config (default: 3 characters)\n const minLength = this.config.minStringLength !== undefined ? this.config.minStringLength : 3\n if (str.trim().length < minLength) {\n return true\n }\n\n // Skip status values\n if (['archived', 'draft', 'pending', 'published'].includes(str.toLowerCase())) {\n return true\n }\n\n // Skip paths ending with id, createdAt, updatedAt, etc.\n const pathLower = path.toLowerCase()\n if (\n pathLower.endsWith('id') ||\n pathLower.endsWith('_id') ||\n pathLower.includes('createdat') ||\n pathLower.includes('updatedat')\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * Legacy translation method (sends entire structure)\n */\n private async translateWithOpenAILegacy(\n data: any,\n fromLocale: string,\n toLocale: string,\n payload: Payload,\n ): Promise<any> {\n const client = this.getOpenAIClient()\n const timeout = this.config.provider?.timeout || 30000\n\n try {\n // Get translation settings from global\n const settings = await this.getTranslationSettings(payload)\n\n // Build system message from settings\n const systemPrompt = settings.systemPrompt\n .replace('{fromLocale}', fromLocale)\n .replace('{toLocale}', toLocale)\n\n const systemMessage = `${systemPrompt}\\n\\n${settings.translationRules}`\n\n const requestParams: any = {\n messages: [\n {\n content: systemMessage,\n role: 'system',\n },\n {\n content: JSON.stringify(data, null, 2),\n role: 'user',\n },\n ],\n model: settings.model,\n response_format: { type: 'json_object' },\n temperature: settings.temperature,\n }\n\n // Add maxTokens if specified\n if (settings.maxTokens) {\n requestParams.max_tokens = settings.maxTokens\n }\n\n const response = await client.chat.completions.create(requestParams, { timeout })\n\n const translatedText = response.choices[0]?.message?.content\n\n if (!translatedText) {\n throw new Error('No translation received from OpenAI')\n }\n\n return JSON.parse(translatedText)\n } catch (error) {\n console.error('[Auto-Translate] Translation error:', error)\n throw error\n }\n }\n\n /**\n * Gets global and collection-specific excluded fields\n */\n getConfigExcludedFields(collection: string): string[] {\n const globalExclusions = this.config.excludeFields || []\n const collectionConfig = this.config.collections?.[collection as CollectionSlug]\n\n if (typeof collectionConfig === 'object' && collectionConfig.excludeFields) {\n return [...globalExclusions, ...collectionConfig.excludeFields]\n }\n\n return globalExclusions\n }\n\n /**\n * Gets translation exclusions for a document\n */\n async getExclusions(\n payload: Payload,\n collection: string,\n documentId: string,\n locale: string,\n ): Promise<string[]> {\n const exclusionsSlug = (\n this.config.translationExclusionsSlug || 'translation-exclusions'\n ) as CollectionSlug\n\n try {\n const result = await payload.find({\n collection: exclusionsSlug,\n limit: 1,\n where: {\n and: [\n { collectionSlug: { equals: collection } },\n { documentId: { equals: documentId } },\n { locale: { equals: locale } },\n ],\n },\n })\n\n if (result.docs.length > 0) {\n const exclusion = result.docs[0] as any\n return exclusion.excludedPaths?.map((item: any) => item.path) || []\n }\n\n return []\n } catch (error) {\n if (this.config.debugging) {\n payload.logger.error(`[Auto-Translate] Error fetching exclusions: ${error}`)\n }\n return []\n }\n }\n\n /**\n * Main translation method\n */\n async translate(options: TranslateOptions): Promise<any> {\n const { collection, data, excludedPaths = [], fromLocale, payload, toLocale } = options\n\n // Filter out excluded paths before translation\n const dataToTranslate = filterExcludedPaths(data, excludedPaths)\n\n if (this.config.debugging) {\n payload.logger.info(\n `[Auto-Translate] Translating from ${fromLocale} to ${toLocale} for collection ${collection}`,\n )\n payload.logger.info(`[Auto-Translate] Excluded paths: ${excludedPaths.join(', ')}`)\n }\n\n // Use custom translator if provided\n if (this.config.provider?.customTranslate) {\n return await this.config.provider.customTranslate(options)\n }\n\n // Use OpenAI by default\n return await this.translateWithOpenAI(dataToTranslate, fromLocale, toLocale, payload)\n }\n\n /**\n * Translates using OpenAI API (optimized version)\n * This method is now public and can be used directly in your application\n */\n async translateWithOpenAI(\n data: any,\n fromLocale: string,\n toLocale: string,\n payload: Payload,\n ): Promise<any> {\n const client = this.getOpenAIClient()\n\n // Use optimization by default (can be disabled via config)\n const useOptimization = this.config.optimizeTranslation !== false\n\n if (!useOptimization) {\n // Use legacy approach: send entire structure\n return this.translateWithOpenAILegacy(data, fromLocale, toLocale, payload)\n }\n\n // Extract only translatable strings with deduplication\n const { deduplicationMap, metadata, strings } = this.extractTranslatableStrings(data)\n\n if (strings.size === 0) {\n // Nothing to translate\n return data\n }\n\n // Create a simple object with just the strings to translate\n const stringsToTranslate: Record<string, string> = {}\n strings.forEach((value, key) => {\n stringsToTranslate[key] = value\n })\n\n if (this.config.debugging) {\n const originalSize = JSON.stringify(data).length\n const optimizedSize = JSON.stringify(stringsToTranslate).length\n const reduction = ((1 - optimizedSize / originalSize) * 100).toFixed(1)\n\n // Calculate deduplication stats\n let totalPaths = 0\n deduplicationMap.forEach((paths) => {\n totalPaths += paths.length\n })\n const deduplicationSavings = totalPaths - strings.size\n const deduplicationPercent =\n totalPaths > 0 ? ((deduplicationSavings / totalPaths) * 100).toFixed(1) : '0'\n\n console.log('[Auto-Translate] ✨ Optimization Stats:')\n console.log(` 📊 Unique strings to translate: ${strings.size}`)\n console.log(` 🔄 Total string instances: ${totalPaths}`)\n console.log(\n ` 💾 Deduplication savings: ${deduplicationSavings} strings (${deduplicationPercent}%)`,\n )\n console.log(` 📦 Original JSON size: ${originalSize.toLocaleString()} bytes`)\n console.log(` 📦 Optimized JSON size: ${optimizedSize.toLocaleString()} bytes`)\n console.log(` 🎯 Total size reduction: ${reduction}%`)\n }\n\n try {\n // Get translation settings from global\n const settings = await this.getTranslationSettings(payload)\n\n // Add timeout configuration (default 30 seconds, configurable via plugin options)\n const timeout = this.config.provider?.timeout || 30000\n\n if (this.config.debugging) {\n console.log(\n `[Auto-Translate] Calling OpenAI API (timeout: ${timeout}ms, model: ${settings.model})`,\n )\n console.log(\n `[Auto-Translate] Payload size: ${JSON.stringify(stringsToTranslate).length} bytes`,\n )\n }\n\n // Build system message from settings\n const systemPrompt = settings.systemPrompt\n .replace('{fromLocale}', fromLocale)\n .replace('{toLocale}', toLocale)\n\n const systemMessage = `${systemPrompt}\\n\\n${settings.translationRules}`\n\n const requestParams: any = {\n messages: [\n {\n content: systemMessage,\n role: 'system',\n },\n {\n content: JSON.stringify(stringsToTranslate, null, 2),\n role: 'user',\n },\n ],\n model: settings.model,\n response_format: { type: 'json_object' },\n temperature: settings.temperature,\n }\n\n // Add maxTokens if specified\n if (settings.maxTokens) {\n requestParams.max_tokens = settings.maxTokens\n }\n\n const response = await client.chat.completions.create(requestParams, { timeout })\n\n const translatedText = response.choices[0]?.message?.content\n\n if (!translatedText) {\n throw new Error('No translation received from OpenAI')\n }\n\n if (this.config.debugging) {\n console.log(\n `[Auto-Translate] Received response from OpenAI (${translatedText.length} chars)`,\n )\n }\n\n let translatedStrings: any\n try {\n translatedStrings = JSON.parse(translatedText)\n } catch (parseError) {\n console.error('[Auto-Translate] Failed to parse OpenAI response as JSON')\n console.error('[Auto-Translate] Response text:', translatedText.substring(0, 500))\n throw new Error(\n `Invalid JSON response from OpenAI: ${parseError instanceof Error ? parseError.message : String(parseError)}`,\n )\n }\n\n // Convert back to Map\n const translationsMap = new Map<string, string>()\n for (const [key, value] of Object.entries(translatedStrings)) {\n if (typeof value === 'string') {\n translationsMap.set(key, value)\n }\n }\n\n // Reconstruct the full data structure with translations, applying deduplication\n return this.reconstructWithTranslations(metadata, translationsMap, deduplicationMap)\n } catch (error) {\n console.error('[Auto-Translate] Translation error:', error)\n\n // Provide more context about the error\n if (error && typeof error === 'object') {\n const err = error as any\n if (err.status) {\n console.error(`[Auto-Translate] OpenAI API status: ${err.status}`)\n }\n if (err.code) {\n console.error(`[Auto-Translate] Error code: ${err.code}`)\n }\n if (err.message) {\n console.error(`[Auto-Translate] Error message: ${err.message}`)\n }\n }\n\n // Add context to the error before re-throwing\n const contextualError = new Error(\n `Translation failed from ${fromLocale} to ${toLocale}: ${error instanceof Error ? error.message : String(error)}`,\n )\n contextualError.cause = error\n throw contextualError\n }\n }\n\n /**\n * Updates translation exclusions for a document\n */\n async updateExclusions(\n payload: Payload,\n collection: string,\n documentId: string,\n locale: string,\n excludedPaths: string[],\n ): Promise<void> {\n const exclusionsSlug = (\n this.config.translationExclusionsSlug || 'translation-exclusions'\n ) as CollectionSlug\n\n try {\n const existing = await payload.find({\n collection: exclusionsSlug,\n limit: 1,\n where: {\n and: [\n { collectionSlug: { equals: collection } },\n { documentId: { equals: documentId } },\n { locale: { equals: locale } },\n ],\n },\n })\n\n const exclusionsData = {\n collectionSlug: collection,\n documentId,\n excludedPaths: excludedPaths.map((path) => ({ path })),\n locale,\n }\n\n if (existing.docs.length > 0) {\n await payload.update({\n id: existing.docs[0].id,\n collection: exclusionsSlug,\n data: exclusionsData,\n })\n } else {\n await payload.create({\n collection: exclusionsSlug,\n data: exclusionsData,\n })\n }\n\n if (this.config.debugging) {\n payload.logger.info(\n `[Auto-Translate] Updated exclusions for ${collection}:${documentId}:${locale}`,\n )\n }\n } catch (error) {\n if (this.config.debugging) {\n payload.logger.error(`[Auto-Translate] Error updating exclusions: ${error}`)\n }\n }\n }\n}\n"],"names":["OpenAI","filterExcludedPaths","TranslationService","client","config","extractFromLexicalNode","node","path","strings","deduplicationMap","enableDeduplication","type","text","trimmed","trim","length","shouldSkipString","textPath","has","existingPaths","get","push","set","children","Array","isArray","map","child","index","extractTranslatableStrings","data","Map","extract","obj","currentPath","undefined","isLexicalEditorNode","item","result","key","value","Object","entries","newPath","trimmedValue","metadata","getOpenAIClient","apiKey","provider","process","env","OPENAI_API_KEY","Error","baseURL","OPENAI_BASE_URL","getOriginalValue","parts","split","filter","Boolean","current","part","getTranslationSettings","payload","settingsSlug","translationSettingsSlug","defaults","maxTokens","model","systemPrompt","temperature","translationRules","settings","findGlobal","slug","error","debugging","console","warn","reconstructWithTranslations","translations","fullTranslations","forEach","translatedValue","originalPath","originalValue","replace","paths","includes","reconstruct","startsWith","slice","str","test","minLength","minStringLength","toLowerCase","pathLower","endsWith","translateWithOpenAILegacy","fromLocale","toLocale","timeout","systemMessage","requestParams","messages","content","role","JSON","stringify","response_format","max_tokens","response","chat","completions","create","translatedText","choices","message","parse","getConfigExcludedFields","collection","globalExclusions","excludeFields","collectionConfig","collections","getExclusions","documentId","locale","exclusionsSlug","translationExclusionsSlug","find","limit","where","and","collectionSlug","equals","docs","exclusion","excludedPaths","logger","translate","options","dataToTranslate","info","join","customTranslate","translateWithOpenAI","useOptimization","optimizeTranslation","size","stringsToTranslate","originalSize","optimizedSize","reduction","toFixed","totalPaths","deduplicationSavings","deduplicationPercent","log","toLocaleString","translatedStrings","parseError","substring","String","translationsMap","err","status","code","contextualError","cause","updateExclusions","existing","exclusionsData","update","id"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pigment/auto-translate",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "Automatic translation plugin for Payload CMS with field-level exclusion controls and performance optimizations",
5
5
  "keywords": [
6
6
  "payload",
@@ -49,32 +49,33 @@
49
49
  ],
50
50
  "devDependencies": {
51
51
  "@eslint/eslintrc": "^3.2.0",
52
- "@payloadcms/db-mongodb": "3.62.1",
53
- "@payloadcms/db-postgres": "3.62.1",
54
- "@payloadcms/db-sqlite": "3.62.1",
55
- "@payloadcms/eslint-config": "3.9.0",
56
- "@payloadcms/next": "3.62.1",
57
- "@payloadcms/richtext-lexical": "3.62.1",
58
- "@payloadcms/ui": "3.62.1",
52
+ "@payloadcms/db-mongodb": "3.85.0",
53
+ "@payloadcms/db-postgres": "3.85.0",
54
+ "@payloadcms/db-sqlite": "3.85.0",
55
+ "@payloadcms/eslint-config": "3.28.0",
56
+ "@payloadcms/next": "3.85.0",
57
+ "@payloadcms/richtext-lexical": "3.85.0",
58
+ "@payloadcms/ui": "3.85.0",
59
59
  "@playwright/test": "^1.52.0",
60
60
  "@swc-node/register": "1.10.9",
61
61
  "@swc/cli": "0.6.0",
62
62
  "@types/node": "^22.5.4",
63
- "@types/react": "19.1.8",
64
- "@types/react-dom": "19.1.6",
63
+ "@types/react": "19.2.17",
64
+ "@types/react-dom": "19.2.3",
65
65
  "copyfiles": "2.4.1",
66
66
  "cross-env": "^7.0.3",
67
+ "dotenv-cli": "^11.0.0",
67
68
  "eslint": "^9.23.0",
68
- "eslint-config-next": "15.4.4",
69
+ "eslint-config-next": "16.2.6",
69
70
  "graphql": "^16.8.1",
70
71
  "mongodb-memory-server": "10.1.4",
71
- "next": "15.4.4",
72
+ "next": "16.2.6",
72
73
  "open": "^10.1.0",
73
- "payload": "3.62.1",
74
+ "payload": "3.85.0",
74
75
  "prettier": "^3.4.2",
75
76
  "qs-esm": "7.0.2",
76
- "react": "19.1.0",
77
- "react-dom": "19.1.0",
77
+ "react": "19.2.6",
78
+ "react-dom": "19.2.6",
78
79
  "rimraf": "3.0.2",
79
80
  "sharp": "0.34.2",
80
81
  "sort-package-json": "^2.10.0",
@@ -83,10 +84,10 @@
83
84
  "vitest": "^3.1.2"
84
85
  },
85
86
  "peerDependencies": {
86
- "payload": "^3.62.1"
87
+ "payload": "^3.85.0"
87
88
  },
88
89
  "engines": {
89
- "node": "^18.20.2 || >=20.9.0",
90
+ "node": "^20.9.0 || >=22",
90
91
  "pnpm": "^9 || ^10"
91
92
  },
92
93
  "registry": "https://registry.npmjs.org/",
@@ -99,10 +100,15 @@
99
100
  "build:types": "tsc --outDir dist --rootDir ./src",
100
101
  "clean": "rimraf {dist,*.tsbuildinfo}",
101
102
  "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
102
- "dev": "next dev dev --turbo",
103
+ "dev": "next dev dev",
103
104
  "dev:generate-importmap": "pnpm dev:payload generate:importmap",
104
105
  "dev:generate-types": "pnpm dev:payload generate:types",
105
- "dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
106
+ "dev:migrate": "pnpm dev:payload migrate",
107
+ "dev:migrate:create": "pnpm dev:payload migrate:create",
108
+ "dev:migrate:down": "pnpm dev:payload migrate:down",
109
+ "dev:migrate:fresh": "pnpm dev:payload migrate:fresh",
110
+ "dev:migrate:status": "pnpm dev:payload migrate:status",
111
+ "dev:payload": "dotenv -e ./dev/.env -- cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
106
112
  "generate:importmap": "pnpm dev:generate-importmap",
107
113
  "generate:types": "pnpm dev:generate-types",
108
114
  "lint": "eslint",