@pigment/auto-translate 1.4.0 → 1.5.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/dist/index.js CHANGED
@@ -152,6 +152,54 @@ export const autoTranslate = (pluginOptions)=>(incomingConfig)=>{
152
152
  if (!collection.hooks.afterOperation) {
153
153
  collection.hooks.afterOperation = [];
154
154
  }
155
+ // ---------------------------------------------------------------
156
+ // Nested-docs compatibility
157
+ // ---------------------------------------------------------------
158
+ // Resolve nested-docs field slugs once, shared by both the
159
+ // beforeChange guard and the afterOperation translation hook below.
160
+ const nestedDocsFieldSlugs = resolveNestedDocsFieldSlugs(pluginOptions);
161
+ // Determine whether this collection actually has a breadcrumbs array
162
+ // field (added by nestedDocsPlugin or manually).
163
+ const hasBreadcrumbsField = nestedDocsFieldSlugs !== null && collection.fields.some((f)=>'name' in f && f.name === nestedDocsFieldSlugs.breadcrumbsSlug && f.type === 'array');
164
+ if (hasBreadcrumbsField && nestedDocsFieldSlugs) {
165
+ const { breadcrumbsSlug } = nestedDocsFieldSlugs;
166
+ // Guard: strip `id` from breadcrumb array items on non-default-locale writes.
167
+ //
168
+ // Root cause: nested-docs' `resaveChildren` afterChange hook re-saves each
169
+ // child document when a parent is updated. For locales where the child has no
170
+ // row yet, `payload.find(child, locale)` falls back to the default locale,
171
+ // returning breadcrumbs that carry the default-locale array-item `id`s.
172
+ // `formatBreadcrumb` preserves those ids via `{ ...breadcrumb, doc, label, url }`.
173
+ // When Payload then writes the child in the secondary locale, Drizzle attempts
174
+ // an INSERT with the same `id` — colliding on the `breadcrumbs.id` PRIMARY KEY
175
+ // (shared across locales) and producing `ValidationError: Value must be unique: id`.
176
+ //
177
+ // Fix: remove `id` from every breadcrumb item in incoming data for any
178
+ // non-default-locale write. Payload will assign fresh per-locale ids on INSERT.
179
+ // This hook fires AFTER nested-docs' `populateBreadcrumbsBeforeChange` (because
180
+ // autoTranslate is registered later), so breadcrumbs are already fully populated
181
+ // before we strip the stale ids.
182
+ if (!collection.hooks.beforeChange) {
183
+ collection.hooks.beforeChange = [];
184
+ }
185
+ collection.hooks.beforeChange.push(async ({ data, req })=>{
186
+ if (!req.locale || req.locale === defaultLocale) return data;
187
+ if (!data[breadcrumbsSlug] || !Array.isArray(data[breadcrumbsSlug])) return data;
188
+ return {
189
+ ...data,
190
+ [breadcrumbsSlug]: data[breadcrumbsSlug].map((item)=>{
191
+ if (item && typeof item === 'object') {
192
+ const { id: _id, ...rest } = item;
193
+ return rest;
194
+ }
195
+ return item;
196
+ })
197
+ };
198
+ });
199
+ if (pluginOptions.debugging) {
200
+ console.log(`[Auto-Translate Plugin] Nested-docs beforeChange guard added for: ${collectionSlug}`);
201
+ }
202
+ }
155
203
  // Main translation hook
156
204
  const translationHook = async ({ operation, req, result })=>{
157
205
  // Only process create and updateByID operations
@@ -209,9 +257,20 @@ export const autoTranslate = (pluginOptions)=>(incomingConfig)=>{
209
257
  }
210
258
  // Get global/collection-level excluded fields
211
259
  const configExcludedFields = translationService.getConfigExcludedFields(collectionSlug);
260
+ // Exclude nested-docs-managed fields from the AI translation payload.
261
+ // `parent` is locale-invariant (the same relationship across all locales)
262
+ // and must never be overwritten with an AI-translated value.
263
+ // `breadcrumbs` are computed and managed entirely by nested-docs; sending
264
+ // them through the AI would produce garbled data and would be overwritten
265
+ // by nested-docs anyway.
266
+ const nestedDocsExcludedFields = nestedDocsFieldSlugs ? [
267
+ nestedDocsFieldSlugs.parentSlug,
268
+ nestedDocsFieldSlugs.breadcrumbsSlug
269
+ ] : [];
212
270
  const allExcludedPaths = [
213
271
  ...excludedPaths,
214
- ...configExcludedFields
272
+ ...configExcludedFields,
273
+ ...nestedDocsExcludedFields
215
274
  ];
216
275
  if (pluginOptions.debugging && allExcludedPaths.length > 0) {
217
276
  req.payload.logger.info(`[Auto-Translate Plugin] Excluded paths for ${targetLocale}: ${allExcludedPaths.join(', ')}`);
@@ -268,6 +327,13 @@ export const autoTranslate = (pluginOptions)=>(incomingConfig)=>{
268
327
  // a Postgres 23505 unique-constraint violation.
269
328
  const strippedArrayIds = stripArrayItemIds(finalData);
270
329
  const updateData = stripSystemFields(strippedArrayIds);
330
+ // Remove nested-docs-managed fields from the update payload entirely.
331
+ // They were already excluded from translation, but defensively delete them
332
+ // here too so a future refactor cannot accidentally re-introduce them.
333
+ if (nestedDocsFieldSlugs) {
334
+ delete updateData[nestedDocsFieldSlugs.parentSlug];
335
+ delete updateData[nestedDocsFieldSlugs.breadcrumbsSlug];
336
+ }
271
337
  // Update the document in the target locale
272
338
  await req.payload.update({
273
339
  id: doc.id,
@@ -284,6 +350,21 @@ export const autoTranslate = (pluginOptions)=>(incomingConfig)=>{
284
350
  req.payload.logger.info(`[Auto-Translate Plugin] Successfully translated ${collectionSlug}:${doc.id} to ${targetLocale}`);
285
351
  }
286
352
  } catch (error) {
353
+ // When @payloadcms/plugin-nested-docs `resaveChildren` re-saves a child
354
+ // document that has already been translated, Drizzle's locale-table upsert
355
+ // uses `ON CONFLICT (id)` as the conflict target. Because we pass a freshly
356
+ // generated UUID for `id`, there is no conflict on `id` — but the existing
357
+ // row's `(_parent_id, _locale)` unique constraint fires instead. Postgres
358
+ // surfaces this as a unique-constraint violation, and Payload/Drizzle maps
359
+ // it to a ValidationError with path "id". In this case the locale row that
360
+ // already exists is valid (it was written by an earlier translation pass),
361
+ // so we skip the write and continue rather than surfacing a false failure.
362
+ if (isLocaleRowAlreadyExistsError(error)) {
363
+ if (pluginOptions.debugging) {
364
+ req.payload.logger.info(`[Auto-Translate Plugin] Skipping ${collectionSlug}:${doc.id} → ${targetLocale}: locale row already exists (Drizzle upsert conflict on _parent_id/_locale). Existing translation is kept.`);
365
+ }
366
+ continue;
367
+ }
287
368
  // Log detailed error information
288
369
  const errorMessage = error instanceof Error ? error.message : String(error);
289
370
  const errorStack = error instanceof Error ? error.stack : undefined;
@@ -332,6 +413,46 @@ export const autoTranslate = (pluginOptions)=>(incomingConfig)=>{
332
413
  }
333
414
  return config;
334
415
  };
416
+ /**
417
+ * Detects the specific error pattern produced when Drizzle's locale-table upsert
418
+ * encounters an already-existing row for (_parent_id, _locale).
419
+ *
420
+ * Root cause: Drizzle issues `INSERT … ON CONFLICT (id) DO UPDATE`, generating a
421
+ * fresh UUID for `id`. Because that UUID is new there is no conflict on `id`, but
422
+ * Postgres fires the unique constraint on `(_parent_id, _locale)` instead. Payload
423
+ * maps this constraint violation to a ValidationError with `{ path: "id", message:
424
+ * "Value must be unique" }`.
425
+ *
426
+ * This happens when a plugin such as `@payloadcms/plugin-nested-docs` re-saves child
427
+ * documents (via its `resaveChildren` afterChange hook) that were already translated
428
+ * in an earlier pass. The existing locale data is valid, so we can safely skip the
429
+ * redundant write.
430
+ */ function isLocaleRowAlreadyExistsError(error) {
431
+ if (!error || typeof error !== 'object') return false;
432
+ const err = error;
433
+ if (err['name'] !== 'ValidationError') return false;
434
+ const data = err['data'];
435
+ if (!data || !Array.isArray(data['errors'])) return false;
436
+ return data['errors'].some((e)=>e['path'] === 'id' && e['message'] === 'Value must be unique');
437
+ }
438
+ /**
439
+ * Resolves the breadcrumbs/parent field slugs used by @payloadcms/plugin-nested-docs.
440
+ *
441
+ * Returns null when nested-docs compat is explicitly disabled (`nestedDocs: false`).
442
+ * Otherwise returns the configured or default slugs so the caller can:
443
+ * 1. Exclude those fields from the AI translation payload.
444
+ * 2. Strip stale default-locale ids from breadcrumb array items before non-default
445
+ * locale writes (preventing the "Value must be unique: id" Postgres PK collision
446
+ * caused by nested-docs' resaveChildren hook).
447
+ */ function resolveNestedDocsFieldSlugs(pluginOptions) {
448
+ const opt = pluginOptions.nestedDocs;
449
+ // Explicit opt-out
450
+ if (opt === false) return null;
451
+ return {
452
+ breadcrumbsSlug: typeof opt === 'object' && opt.breadcrumbsFieldSlug ? opt.breadcrumbsFieldSlug : 'breadcrumbs',
453
+ parentSlug: typeof opt === 'object' && opt.parentFieldSlug ? opt.parentFieldSlug : 'parent'
454
+ };
455
+ }
335
456
  /**
336
457
  * Helper function to get nested value from object using dot notation
337
458
  */ function getNestedValue(obj, path) {
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\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
+ {"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 // ---------------------------------------------------------------\n // Nested-docs compatibility\n // ---------------------------------------------------------------\n // Resolve nested-docs field slugs once, shared by both the\n // beforeChange guard and the afterOperation translation hook below.\n const nestedDocsFieldSlugs = resolveNestedDocsFieldSlugs(pluginOptions)\n\n // Determine whether this collection actually has a breadcrumbs array\n // field (added by nestedDocsPlugin or manually).\n const hasBreadcrumbsField =\n nestedDocsFieldSlugs !== null &&\n collection.fields.some(\n (f) =>\n 'name' in f &&\n f.name === nestedDocsFieldSlugs.breadcrumbsSlug &&\n f.type === 'array',\n )\n\n if (hasBreadcrumbsField && nestedDocsFieldSlugs) {\n const { breadcrumbsSlug } = nestedDocsFieldSlugs\n\n // Guard: strip `id` from breadcrumb array items on non-default-locale writes.\n //\n // Root cause: nested-docs' `resaveChildren` afterChange hook re-saves each\n // child document when a parent is updated. For locales where the child has no\n // row yet, `payload.find(child, locale)` falls back to the default locale,\n // returning breadcrumbs that carry the default-locale array-item `id`s.\n // `formatBreadcrumb` preserves those ids via `{ ...breadcrumb, doc, label, url }`.\n // When Payload then writes the child in the secondary locale, Drizzle attempts\n // an INSERT with the same `id` — colliding on the `breadcrumbs.id` PRIMARY KEY\n // (shared across locales) and producing `ValidationError: Value must be unique: id`.\n //\n // Fix: remove `id` from every breadcrumb item in incoming data for any\n // non-default-locale write. Payload will assign fresh per-locale ids on INSERT.\n // This hook fires AFTER nested-docs' `populateBreadcrumbsBeforeChange` (because\n // autoTranslate is registered later), so breadcrumbs are already fully populated\n // before we strip the stale ids.\n if (!collection.hooks.beforeChange) {\n collection.hooks.beforeChange = []\n }\n collection.hooks.beforeChange.push(async ({ data, req }: any) => {\n if (!req.locale || req.locale === defaultLocale) return data\n if (!data[breadcrumbsSlug] || !Array.isArray(data[breadcrumbsSlug])) return data\n return {\n ...data,\n [breadcrumbsSlug]: data[breadcrumbsSlug].map((item: any) => {\n if (item && typeof item === 'object') {\n const { id: _id, ...rest } = item as Record<string, unknown>\n return rest\n }\n return item\n }),\n }\n })\n\n if (pluginOptions.debugging) {\n console.log(\n `[Auto-Translate Plugin] Nested-docs beforeChange guard added for: ${collectionSlug}`,\n )\n }\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\n // Exclude nested-docs-managed fields from the AI translation payload.\n // `parent` is locale-invariant (the same relationship across all locales)\n // and must never be overwritten with an AI-translated value.\n // `breadcrumbs` are computed and managed entirely by nested-docs; sending\n // them through the AI would produce garbled data and would be overwritten\n // by nested-docs anyway.\n const nestedDocsExcludedFields = nestedDocsFieldSlugs\n ? [nestedDocsFieldSlugs.parentSlug, nestedDocsFieldSlugs.breadcrumbsSlug]\n : []\n\n const allExcludedPaths = [\n ...excludedPaths,\n ...configExcludedFields,\n ...nestedDocsExcludedFields,\n ]\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 // Remove nested-docs-managed fields from the update payload entirely.\n // They were already excluded from translation, but defensively delete them\n // here too so a future refactor cannot accidentally re-introduce them.\n if (nestedDocsFieldSlugs) {\n delete updateData[nestedDocsFieldSlugs.parentSlug]\n delete updateData[nestedDocsFieldSlugs.breadcrumbsSlug]\n }\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 // When @payloadcms/plugin-nested-docs `resaveChildren` re-saves a child\n // document that has already been translated, Drizzle's locale-table upsert\n // uses `ON CONFLICT (id)` as the conflict target. Because we pass a freshly\n // generated UUID for `id`, there is no conflict on `id` — but the existing\n // row's `(_parent_id, _locale)` unique constraint fires instead. Postgres\n // surfaces this as a unique-constraint violation, and Payload/Drizzle maps\n // it to a ValidationError with path \"id\". In this case the locale row that\n // already exists is valid (it was written by an earlier translation pass),\n // so we skip the write and continue rather than surfacing a false failure.\n if (isLocaleRowAlreadyExistsError(error)) {\n if (pluginOptions.debugging) {\n req.payload.logger.info(\n `[Auto-Translate Plugin] Skipping ${collectionSlug}:${doc.id} → ${targetLocale}: locale row already exists (Drizzle upsert conflict on _parent_id/_locale). Existing translation is kept.`,\n )\n }\n continue\n }\n\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 * Detects the specific error pattern produced when Drizzle's locale-table upsert\n * encounters an already-existing row for (_parent_id, _locale).\n *\n * Root cause: Drizzle issues `INSERT … ON CONFLICT (id) DO UPDATE`, generating a\n * fresh UUID for `id`. Because that UUID is new there is no conflict on `id`, but\n * Postgres fires the unique constraint on `(_parent_id, _locale)` instead. Payload\n * maps this constraint violation to a ValidationError with `{ path: \"id\", message:\n * \"Value must be unique\" }`.\n *\n * This happens when a plugin such as `@payloadcms/plugin-nested-docs` re-saves child\n * documents (via its `resaveChildren` afterChange hook) that were already translated\n * in an earlier pass. The existing locale data is valid, so we can safely skip the\n * redundant write.\n */\nfunction isLocaleRowAlreadyExistsError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false\n const err = error as Record<string, unknown>\n if (err['name'] !== 'ValidationError') return false\n const data = err['data'] as Record<string, unknown> | undefined\n if (!data || !Array.isArray(data['errors'])) return false\n return (data['errors'] as Array<Record<string, unknown>>).some(\n (e) => e['path'] === 'id' && e['message'] === 'Value must be unique',\n )\n}\n\n/**\n * Resolves the breadcrumbs/parent field slugs used by @payloadcms/plugin-nested-docs.\n *\n * Returns null when nested-docs compat is explicitly disabled (`nestedDocs: false`).\n * Otherwise returns the configured or default slugs so the caller can:\n * 1. Exclude those fields from the AI translation payload.\n * 2. Strip stale default-locale ids from breadcrumb array items before non-default\n * locale writes (preventing the \"Value must be unique: id\" Postgres PK collision\n * caused by nested-docs' resaveChildren hook).\n */\nfunction resolveNestedDocsFieldSlugs(\n pluginOptions: AutoTranslateConfig,\n): { breadcrumbsSlug: string; parentSlug: string } | null {\n const opt = pluginOptions.nestedDocs\n // Explicit opt-out\n if (opt === false) return null\n return {\n breadcrumbsSlug:\n typeof opt === 'object' && opt.breadcrumbsFieldSlug ? opt.breadcrumbsFieldSlug : 'breadcrumbs',\n parentSlug:\n typeof opt === 'object' && opt.parentFieldSlug ? opt.parentFieldSlug : 'parent',\n }\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","nestedDocsFieldSlugs","resolveNestedDocsFieldSlugs","hasBreadcrumbsField","some","f","breadcrumbsSlug","beforeChange","push","req","locale","translationHook","operation","payload","logger","error","JSON","stringify","doc","info","_status","translationSync","secondaryLocales","filter","targetLocale","excludedPaths","getExclusions","toString","configExcludedFields","getConfigExcludedFields","nestedDocsExcludedFields","parentSlug","allExcludedPaths","length","join","existingDoc","existingResult","findByID","fallbackLocale","translatedData","translate","fromLocale","toLocale","finalData","excludedPath","existingValue","getNestedValue","undefined","setNestedValue","strippedArrayIds","updateData","update","context","skipAutoTranslate","isLocaleRowAlreadyExistsError","errorMessage","Error","message","String","errorStack","stack","existingHooks","args","hook","hookResult","err","e","opt","nestedDocs","breadcrumbsFieldSlug","parentFieldSlug","obj","path","split","reduce","current","part","parts","i","nextPart","test"],"mappings":"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"}
@@ -53,6 +53,12 @@ export declare class TranslationService {
53
53
  * Main translation method
54
54
  */
55
55
  translate(options: TranslateOptions): Promise<any>;
56
+ /**
57
+ * Resolves the field schema for a collection or global slug so translation can
58
+ * be made schema-aware (e.g. to avoid translating enum-backed select/radio
59
+ * field values).
60
+ */
61
+ private getDocumentFields;
56
62
  /**
57
63
  * Translates using OpenAI API (optimized version)
58
64
  * This method is now public and can be used directly in your application
@@ -1,5 +1,5 @@
1
1
  import OpenAI from 'openai';
2
- import { filterExcludedPaths } from '../utilities/fieldHelpers.js';
2
+ import { filterExcludedPaths, overlayNonTranslatableValues } from '../utilities/fieldHelpers.js';
3
3
  export class TranslationService {
4
4
  client;
5
5
  config;
@@ -422,12 +422,44 @@ export class TranslationService {
422
422
  payload.logger.info(`[Auto-Translate] Translating from ${fromLocale} to ${toLocale} for collection ${collection}`);
423
423
  payload.logger.info(`[Auto-Translate] Excluded paths: ${excludedPaths.join(', ')}`);
424
424
  }
425
- // Use custom translator if provided
425
+ // Run the configured translation strategy
426
+ let translated;
426
427
  if (this.config.provider?.customTranslate) {
427
- return await this.config.provider.customTranslate(options);
428
+ // Use custom translator if provided
429
+ translated = await this.config.provider.customTranslate(options);
430
+ } else {
431
+ // Use OpenAI by default
432
+ translated = await this.translateWithOpenAI(dataToTranslate, fromLocale, toLocale, payload);
428
433
  }
429
- // Use OpenAI by default
430
- return await this.translateWithOpenAI(dataToTranslate, fromLocale, toLocale, payload);
434
+ // Restore canonical values for enum-backed fields (select/radio). The Postgres
435
+ // adapter stores these as native enum columns, so a translated option value
436
+ // (e.g. "narrow" -> "schmal") is rejected with `invalid input value for enum`.
437
+ //
438
+ // When `translateLocalizedFieldsOnly` is enabled, also restore every field that
439
+ // is not localized (directly or via a localized ancestor container) so only
440
+ // localized fields are translated.
441
+ const fields = this.getDocumentFields(payload, collection);
442
+ if (fields) {
443
+ overlayNonTranslatableValues(translated, data, fields, {
444
+ localizedOnly: this.config.translateLocalizedFieldsOnly === true
445
+ });
446
+ }
447
+ return translated;
448
+ }
449
+ /**
450
+ * Resolves the field schema for a collection or global slug so translation can
451
+ * be made schema-aware (e.g. to avoid translating enum-backed select/radio
452
+ * field values).
453
+ */ getDocumentFields(payload, slug) {
454
+ const collectionConfig = payload.collections?.[slug]?.config;
455
+ if (collectionConfig && Array.isArray(collectionConfig.fields)) {
456
+ return collectionConfig.fields;
457
+ }
458
+ const globalConfig = payload.config?.globals?.find((g)=>g.slug === slug);
459
+ if (globalConfig && Array.isArray(globalConfig.fields)) {
460
+ return globalConfig.fields;
461
+ }
462
+ return undefined;
431
463
  }
432
464
  /**
433
465
  * Translates using OpenAI API (optimized version)
@@ -1 +1 @@
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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"}
1
+ {"version":3,"sources":["../../src/services/translationService.ts"],"sourcesContent":["import type { CollectionSlug, Field, GlobalSlug, Payload } from 'payload'\n\nimport OpenAI from 'openai'\n\nimport type { AutoTranslateConfig, TranslateOptions } from '../types/index.js'\n\nimport { filterExcludedPaths, overlayNonTranslatableValues } 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 = (this.config.translationExclusionsSlug ||\n 'translation-exclusions') 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 // Run the configured translation strategy\n let translated: any\n if (this.config.provider?.customTranslate) {\n // Use custom translator if provided\n translated = await this.config.provider.customTranslate(options)\n } else {\n // Use OpenAI by default\n translated = await this.translateWithOpenAI(dataToTranslate, fromLocale, toLocale, payload)\n }\n\n // Restore canonical values for enum-backed fields (select/radio). The Postgres\n // adapter stores these as native enum columns, so a translated option value\n // (e.g. \"narrow\" -> \"schmal\") is rejected with `invalid input value for enum`.\n //\n // When `translateLocalizedFieldsOnly` is enabled, also restore every field that\n // is not localized (directly or via a localized ancestor container) so only\n // localized fields are translated.\n const fields = this.getDocumentFields(payload, collection)\n if (fields) {\n overlayNonTranslatableValues(translated, data, fields, {\n localizedOnly: this.config.translateLocalizedFieldsOnly === true,\n })\n }\n\n return translated\n }\n\n /**\n * Resolves the field schema for a collection or global slug so translation can\n * be made schema-aware (e.g. to avoid translating enum-backed select/radio\n * field values).\n */\n private getDocumentFields(payload: Payload, slug: string): Field[] | undefined {\n const collectionConfig = (payload as any).collections?.[slug]?.config\n if (collectionConfig && Array.isArray(collectionConfig.fields)) {\n return collectionConfig.fields\n }\n\n const globalConfig = (payload.config as any)?.globals?.find((g: any) => g.slug === slug)\n if (globalConfig && Array.isArray(globalConfig.fields)) {\n return globalConfig.fields\n }\n\n return undefined\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 ||\n 'translation-exclusions') 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","overlayNonTranslatableValues","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","translated","customTranslate","translateWithOpenAI","fields","getDocumentFields","localizedOnly","translateLocalizedFieldsOnly","globalConfig","globals","g","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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"}
@@ -45,6 +45,26 @@ export type AutoTranslateConfig = {
45
45
  * This helps avoid translating single characters, dashes, spaces, etc.
46
46
  */
47
47
  minStringLength?: number;
48
+ /**
49
+ * Enable compatibility with @payloadcms/plugin-nested-docs (default: true).
50
+ *
51
+ * When truthy, auto-translate will:
52
+ * 1. Exclude the nested-docs-managed fields (`parent` and `breadcrumbs`) from
53
+ * the AI translation payload so they are never mistranslated.
54
+ * 2. Add a beforeChange hook that strips `id` from breadcrumb array items on
55
+ * non-default-locale writes, preventing the "Value must be unique: id"
56
+ * Postgres constraint error caused by nested-docs' resaveChildren hook
57
+ * reusing the default-locale breadcrumb ids in secondary locales.
58
+ *
59
+ * Set to `false` to disable (if you're not using nested-docs).
60
+ * Pass an object to override the default field slugs used by nested-docs.
61
+ */
62
+ nestedDocs?: boolean | {
63
+ /** Slug of the breadcrumbs array field (default: 'breadcrumbs') */
64
+ breadcrumbsFieldSlug?: string;
65
+ /** Slug of the parent relationship field (default: 'parent') */
66
+ parentFieldSlug?: string;
67
+ };
48
68
  /**
49
69
  * Use optimized translation that extracts only translatable strings (default: true)
50
70
  * This dramatically reduces API payload size and improves translation speed for large documents
@@ -64,6 +84,19 @@ export type AutoTranslateConfig = {
64
84
  timeout?: number;
65
85
  type: 'custom' | 'openai';
66
86
  };
87
+ /**
88
+ * Only translate fields that have localization enabled (default: false).
89
+ *
90
+ * When enabled, a field is translated only if it is `localized: true` — either
91
+ * directly, or by inheriting localization from an ancestor container that is
92
+ * localized (`group`, `array`, `blocks`, or a named `tab`). Every non-localized
93
+ * field keeps its source value and is excluded from translation.
94
+ *
95
+ * This reflects Payload's data model: only localized fields store per-locale
96
+ * values, so translating a non-localized field would overwrite the single
97
+ * shared value across all locales.
98
+ */
99
+ translateLocalizedFieldsOnly?: boolean;
67
100
  /**
68
101
  * Collection slug for storing translation exclusions metadata
69
102
  */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["import type { CollectionSlug, Payload } from 'payload'\n\nexport type AutoTranslateConfig = {\n /**\n * Auto-inject translation control UI into all localized fields (default: true)\n * Note: This is ignored if enableExclusions is false\n */\n autoInjectUI?: boolean\n\n /**\n * List of collections to enable auto-translation\n */\n collections?: Partial<Record<CollectionSlug, boolean | CollectionTranslateConfig>>\n\n /**\n * Show debug logs\n */\n debugging?: boolean\n\n /**\n * Disable the plugin entirely\n */\n disabled?: boolean\n\n /**\n * Enable deduplication of identical strings (default: true)\n * When enabled, identical strings are only translated once and reused\n * This significantly speeds up translation for documents with repeated content\n */\n enableDeduplication?: boolean\n\n /**\n * Enable field-level translation exclusions (default: true)\n * When disabled:\n * - Translation exclusions collection is hidden\n * - Translation control buttons are not added to fields\n * - All localized fields are always translated\n */\n enableExclusions?: boolean\n\n /**\n * Enable translation sync by default\n */\n enableTranslationSyncByDefault?: boolean\n\n /**\n * Fields to exclude from translation globally\n */\n excludeFields?: string[]\n\n /**\n * Minimum string length to translate (default: 3)\n * Strings shorter than this (after trimming) will be skipped\n * This helps avoid translating single characters, dashes, spaces, etc.\n */\n minStringLength?: number\n\n /**\n * Use optimized translation that extracts only translatable strings (default: true)\n * This dramatically reduces API payload size and improves translation speed for large documents\n */\n optimizeTranslation?: boolean\n\n /**\n * Translation provider settings\n */\n provider?: {\n apiKey?: string\n baseURL?: string\n customTranslate?: (options: TranslateOptions) => Promise<any>\n model?: string\n /**\n * Timeout for translation requests in milliseconds (default: 30000)\n */\n timeout?: number\n type: 'custom' | 'openai'\n }\n\n /**\n * Collection slug for storing translation exclusions metadata\n */\n translationExclusionsSlug?: string\n\n /**\n * Global slug for translation settings UI\n */\n translationSettingsSlug?: string\n}\n\nexport type CollectionTranslateConfig = {\n /**\n * Enable translation for this collection\n */\n enabled?: boolean\n\n /**\n * Fields to exclude from translation for this specific collection\n */\n excludeFields?: string[]\n}\n\nexport type TranslateOptions = {\n collection: CollectionSlug\n data: any\n excludedPaths?: string[]\n fromLocale: string\n payload: Payload\n toLocale: string\n}\n\nexport type TranslationExclusion = {\n collectionSlug: CollectionSlug\n createdAt?: string\n documentId: string\n excludedPaths: string[] // Field paths like 'title', 'content.0.description'\n id?: string\n locale: string\n updatedAt?: string\n}\n\nexport type FieldPath = {\n parentPath?: string\n path: string\n value: any\n}\n"],"names":[],"mappings":"AAwHA,WAIC"}
1
+ {"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["import type { CollectionSlug, Payload } from 'payload'\n\nexport type AutoTranslateConfig = {\n /**\n * Auto-inject translation control UI into all localized fields (default: true)\n * Note: This is ignored if enableExclusions is false\n */\n autoInjectUI?: boolean\n\n /**\n * List of collections to enable auto-translation\n */\n collections?: Partial<Record<CollectionSlug, boolean | CollectionTranslateConfig>>\n\n /**\n * Show debug logs\n */\n debugging?: boolean\n\n /**\n * Disable the plugin entirely\n */\n disabled?: boolean\n\n /**\n * Enable deduplication of identical strings (default: true)\n * When enabled, identical strings are only translated once and reused\n * This significantly speeds up translation for documents with repeated content\n */\n enableDeduplication?: boolean\n\n /**\n * Enable field-level translation exclusions (default: true)\n * When disabled:\n * - Translation exclusions collection is hidden\n * - Translation control buttons are not added to fields\n * - All localized fields are always translated\n */\n enableExclusions?: boolean\n\n /**\n * Enable translation sync by default\n */\n enableTranslationSyncByDefault?: boolean\n\n /**\n * Fields to exclude from translation globally\n */\n excludeFields?: string[]\n\n /**\n * Minimum string length to translate (default: 3)\n * Strings shorter than this (after trimming) will be skipped\n * This helps avoid translating single characters, dashes, spaces, etc.\n */\n minStringLength?: number\n\n /**\n * Enable compatibility with @payloadcms/plugin-nested-docs (default: true).\n *\n * When truthy, auto-translate will:\n * 1. Exclude the nested-docs-managed fields (`parent` and `breadcrumbs`) from\n * the AI translation payload so they are never mistranslated.\n * 2. Add a beforeChange hook that strips `id` from breadcrumb array items on\n * non-default-locale writes, preventing the \"Value must be unique: id\"\n * Postgres constraint error caused by nested-docs' resaveChildren hook\n * reusing the default-locale breadcrumb ids in secondary locales.\n *\n * Set to `false` to disable (if you're not using nested-docs).\n * Pass an object to override the default field slugs used by nested-docs.\n */\n nestedDocs?:\n | boolean\n | {\n /** Slug of the breadcrumbs array field (default: 'breadcrumbs') */\n breadcrumbsFieldSlug?: string\n /** Slug of the parent relationship field (default: 'parent') */\n parentFieldSlug?: string\n }\n\n /**\n * Use optimized translation that extracts only translatable strings (default: true)\n * This dramatically reduces API payload size and improves translation speed for large documents\n */\n optimizeTranslation?: boolean\n\n /**\n * Translation provider settings\n */\n provider?: {\n apiKey?: string\n baseURL?: string\n customTranslate?: (options: TranslateOptions) => Promise<any>\n model?: string\n /**\n * Timeout for translation requests in milliseconds (default: 30000)\n */\n timeout?: number\n type: 'custom' | 'openai'\n }\n\n /**\n * Only translate fields that have localization enabled (default: false).\n *\n * When enabled, a field is translated only if it is `localized: true` — either\n * directly, or by inheriting localization from an ancestor container that is\n * localized (`group`, `array`, `blocks`, or a named `tab`). Every non-localized\n * field keeps its source value and is excluded from translation.\n *\n * This reflects Payload's data model: only localized fields store per-locale\n * values, so translating a non-localized field would overwrite the single\n * shared value across all locales.\n */\n translateLocalizedFieldsOnly?: boolean\n\n /**\n * Collection slug for storing translation exclusions metadata\n */\n translationExclusionsSlug?: string\n\n /**\n * Global slug for translation settings UI\n */\n translationSettingsSlug?: string\n}\n\nexport type CollectionTranslateConfig = {\n /**\n * Enable translation for this collection\n */\n enabled?: boolean\n\n /**\n * Fields to exclude from translation for this specific collection\n */\n excludeFields?: string[]\n}\n\nexport type TranslateOptions = {\n collection: CollectionSlug\n data: any\n excludedPaths?: string[]\n fromLocale: string\n payload: Payload\n toLocale: string\n}\n\nexport type TranslationExclusion = {\n collectionSlug: CollectionSlug\n createdAt?: string\n documentId: string\n excludedPaths: string[] // Field paths like 'title', 'content.0.description'\n id?: string\n locale: string\n updatedAt?: string\n}\n\nexport type FieldPath = {\n parentPath?: string\n path: string\n value: any\n}\n"],"names":[],"mappings":"AA6JA,WAIC"}
@@ -1,5 +1,31 @@
1
1
  import type { Field } from 'payload';
2
2
  import type { FieldPath } from '../types/index.js';
3
+ export type OverlayOptions = {
4
+ /**
5
+ * Localization inherited from an ancestor container (`group`/`array`/`blocks`/
6
+ * named `tab`) that has `localized: true`. When true, every nested field is
7
+ * treated as localized.
8
+ */
9
+ inheritedLocalized?: boolean;
10
+ /**
11
+ * When true, only fields that are localized (directly or via an ancestor
12
+ * container) are kept from `translated`; every non-localized field is restored
13
+ * from `original` so it is effectively excluded from translation.
14
+ */
15
+ localizedOnly?: boolean;
16
+ };
17
+ /**
18
+ * Walks a Payload field schema and copies canonical (untranslated) values from
19
+ * `original` back into `translated`, in place. A field's value is restored when:
20
+ *
21
+ * - it is a `select`/`radio` field (enum-backed; always restored), or
22
+ * - `localizedOnly` is enabled and the field is not localized (directly or via
23
+ * a localized ancestor container).
24
+ *
25
+ * This works regardless of which translation strategy produced `translated`
26
+ * (optimized, legacy, or a custom translator).
27
+ */
28
+ export declare function overlayNonTranslatableValues(translated: any, original: any, fields: Field[] | undefined, options?: OverlayOptions): void;
3
29
  /**
4
30
  * Recursively extracts all field paths and their values from a document
5
31
  */
@@ -1,3 +1,122 @@
1
+ /**
2
+ * Field types whose stored value is an option/enum constant rather than
3
+ * human-readable prose. The Postgres adapter persists these as native `enum`
4
+ * columns, so translating their values (e.g. "narrow" -> "schmal") produces an
5
+ * invalid enum value and makes the locale-row INSERT fail with
6
+ * `invalid input value for enum`. These must never be sent to the translator.
7
+ */ const NON_TRANSLATABLE_FIELD_TYPES = new Set([
8
+ 'select',
9
+ 'radio'
10
+ ]);
11
+ /**
12
+ * Container field types that hold nested fields rather than a leaf value.
13
+ * Localization (`localized: true`) set on a named container (`group`, `array`,
14
+ * `blocks`, named `tab`) cascades to every field nested within it.
15
+ */ const CONTAINER_FIELD_TYPES = new Set([
16
+ 'array',
17
+ 'blocks',
18
+ 'collapsible',
19
+ 'group',
20
+ 'row',
21
+ 'tabs'
22
+ ]);
23
+ /**
24
+ * Walks a Payload field schema and copies canonical (untranslated) values from
25
+ * `original` back into `translated`, in place. A field's value is restored when:
26
+ *
27
+ * - it is a `select`/`radio` field (enum-backed; always restored), or
28
+ * - `localizedOnly` is enabled and the field is not localized (directly or via
29
+ * a localized ancestor container).
30
+ *
31
+ * This works regardless of which translation strategy produced `translated`
32
+ * (optimized, legacy, or a custom translator).
33
+ */ export function overlayNonTranslatableValues(translated, original, fields, options = {}) {
34
+ if (!Array.isArray(fields) || !translated || typeof translated !== 'object' || !original || typeof original !== 'object') {
35
+ return;
36
+ }
37
+ const { inheritedLocalized = false, localizedOnly = false } = options;
38
+ for (const field of fields){
39
+ const type = field?.type;
40
+ const name = typeof field?.name === 'string' ? field.name : undefined;
41
+ const isLocalized = inheritedLocalized || field?.localized === true;
42
+ // Container fields: recurse, cascading localization to nested fields.
43
+ if (type && CONTAINER_FIELD_TYPES.has(type)) {
44
+ const childOptions = {
45
+ inheritedLocalized: isLocalized,
46
+ localizedOnly
47
+ };
48
+ switch(type){
49
+ case 'array':
50
+ if (name && Array.isArray(translated[name]) && Array.isArray(original[name])) {
51
+ translated[name].forEach((item, index)=>{
52
+ overlayNonTranslatableValues(item, original[name][index], field.fields, childOptions);
53
+ });
54
+ } else if (localizedOnly && name && !isLocalized && original[name] !== undefined) {
55
+ translated[name] = original[name];
56
+ }
57
+ break;
58
+ case 'blocks':
59
+ if (name && Array.isArray(translated[name]) && Array.isArray(original[name])) {
60
+ const blockDefs = Array.isArray(field.blocks) ? field.blocks : [];
61
+ translated[name].forEach((item, index)=>{
62
+ const originalItem = original[name][index];
63
+ const blockType = item?.blockType ?? originalItem?.blockType;
64
+ const blockDef = blockDefs.find((b)=>b?.slug === blockType);
65
+ if (blockDef) {
66
+ overlayNonTranslatableValues(item, originalItem, blockDef.fields, childOptions);
67
+ }
68
+ });
69
+ } else if (localizedOnly && name && !isLocalized && original[name] !== undefined) {
70
+ translated[name] = original[name];
71
+ }
72
+ break;
73
+ case 'collapsible':
74
+ case 'row':
75
+ // Presentational wrappers share the parent object and cannot be localized.
76
+ overlayNonTranslatableValues(translated, original, field.fields, {
77
+ inheritedLocalized,
78
+ localizedOnly
79
+ });
80
+ break;
81
+ case 'group':
82
+ if (name) {
83
+ overlayNonTranslatableValues(translated[name], original[name], field.fields, childOptions);
84
+ }
85
+ break;
86
+ case 'tabs':
87
+ if (Array.isArray(field.tabs)) {
88
+ for (const tab of field.tabs){
89
+ const tabName = typeof tab?.name === 'string' ? tab.name : undefined;
90
+ const tabLocalized = inheritedLocalized || tab?.localized === true;
91
+ if (tabName) {
92
+ overlayNonTranslatableValues(translated[tabName], original[tabName], tab.fields, {
93
+ inheritedLocalized: tabLocalized,
94
+ localizedOnly
95
+ });
96
+ } else {
97
+ // Unnamed tabs share the parent object.
98
+ overlayNonTranslatableValues(translated, original, tab.fields, {
99
+ inheritedLocalized,
100
+ localizedOnly
101
+ });
102
+ }
103
+ }
104
+ }
105
+ break;
106
+ }
107
+ continue;
108
+ }
109
+ // Leaf fields: restore the original value when it must not be translated.
110
+ if (!name) {
111
+ continue;
112
+ }
113
+ const isEnumField = Boolean(type && NON_TRANSLATABLE_FIELD_TYPES.has(type));
114
+ const shouldRestore = isEnumField || localizedOnly && !isLocalized;
115
+ if (shouldRestore && original[name] !== undefined) {
116
+ translated[name] = original[name];
117
+ }
118
+ }
119
+ }
1
120
  /**
2
121
  * Recursively extracts all field paths and their values from a document
3
122
  */ export function extractFieldPaths(data, parentPath = '', fields) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/fieldHelpers.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { FieldPath } from '../types/index.js'\n\n/**\n * Recursively extracts all field paths and their values from a document\n */\nexport function extractFieldPaths(\n data: any,\n parentPath: string = '',\n fields?: Field[],\n): FieldPath[] {\n const paths: FieldPath[] = []\n\n if (!data || typeof data !== 'object') {\n return paths\n }\n\n for (const [key, value] of Object.entries(data)) {\n const currentPath = parentPath ? `${parentPath}.${key}` : key\n\n // Skip internal fields\n if (\n key === 'id' ||\n key === '_id' ||\n key === 'createdAt' ||\n key === 'updatedAt' ||\n key === 'translationSync' ||\n key === '__v'\n ) {\n continue\n }\n\n // Add current field\n paths.push({\n parentPath,\n path: currentPath,\n value,\n })\n\n // Recursively process nested objects\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n paths.push(...extractFieldPaths(value, currentPath, fields))\n }\n\n // Process arrays\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n const arrayPath = `${currentPath}.${index}`\n if (item && typeof item === 'object') {\n paths.push(...extractFieldPaths(item, arrayPath, fields))\n }\n })\n }\n }\n\n return paths\n}\n\n/**\n * Filters out excluded paths from data before translation\n */\nexport function filterExcludedPaths(data: any, excludedPaths: string[]): any {\n if (!data || typeof data !== 'object' || excludedPaths.length === 0) {\n return data\n }\n\n const filtered = JSON.parse(JSON.stringify(data)) // Deep clone\n\n for (const excludedPath of excludedPaths) {\n deletePath(filtered, excludedPath)\n }\n\n return filtered\n}\n\n/**\n * Merges translated data back, respecting excluded paths\n */\nexport function mergeTranslatedData(\n originalData: any,\n translatedData: any,\n excludedPaths: string[],\n): any {\n if (!translatedData || typeof translatedData !== 'object') {\n return originalData\n }\n\n const merged = JSON.parse(JSON.stringify(originalData)) // Deep clone\n\n function merge(target: any, source: any, currentPath: string = ''): void {\n for (const key in source) {\n const fullPath = currentPath ? `${currentPath}.${key}` : key\n\n // Skip if this path is excluded\n if (isPathExcluded(fullPath, excludedPaths)) {\n continue\n }\n\n // Skip internal fields\n if (\n key === 'id' ||\n key === '_id' ||\n key === 'createdAt' ||\n key === 'updatedAt' ||\n key === 'translationSync' ||\n key === '__v'\n ) {\n continue\n }\n\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n if (!target[key]) {\n target[key] = {}\n }\n merge(target[key], source[key], fullPath)\n } else {\n target[key] = source[key]\n }\n }\n }\n\n merge(merged, translatedData)\n return merged\n}\n\n/**\n * Checks if a path is excluded or if any parent path is excluded\n */\nexport function isPathExcluded(path: string, excludedPaths: string[]): boolean {\n return excludedPaths.some((excludedPath) => {\n // Exact match\n if (path === excludedPath) {\n return true\n }\n\n // Check if path is a child of excluded path\n if (path.startsWith(`${excludedPath}.`)) {\n return true\n }\n\n // Check if excluded path is a pattern match for arrays (e.g., content.0.title)\n const pathParts = path.split('.')\n const excludedParts = excludedPath.split('.')\n\n for (let i = 0; i < Math.min(pathParts.length, excludedParts.length); i++) {\n if (excludedParts[i] !== pathParts[i]) {\n // Check if it's an array index difference\n if (!isNaN(Number(pathParts[i])) && !isNaN(Number(excludedParts[i]))) {\n continue\n }\n return false\n }\n }\n\n return excludedParts.length <= pathParts.length\n })\n}\n\n/**\n * Deletes a path from an object using dot notation\n */\nfunction deletePath(obj: any, path: string): void {\n const parts = path.split('.')\n let current = obj\n\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) {\n return\n }\n current = current[parts[i]]\n }\n\n delete current[parts[parts.length - 1]]\n}\n\n/**\n * Gets value at path using dot notation\n */\nexport function getValueAtPath(obj: any, path: string): any {\n return path.split('.').reduce((current, part) => current?.[part], obj)\n}\n\n/**\n * Sets value at path using dot notation\n */\nexport function setValueAtPath(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 if (!current[parts[i]]) {\n current[parts[i]] = {}\n }\n current = current[parts[i]]\n }\n\n current[parts[parts.length - 1]] = value\n}\n\n/**\n * Checks if a field is a localized field\n */\nexport function isLocalizedField(field: Field): boolean {\n return 'localized' in field && field.localized === true\n}\n\n/**\n * Gets all localized field paths from a collection config\n */\nexport function getLocalizedFieldPaths(fields: Field[], parentPath: string = ''): string[] {\n const paths: string[] = []\n\n for (const field of fields) {\n if (!('name' in field)) {\n continue\n }\n\n const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name\n\n if (isLocalizedField(field)) {\n paths.push(fieldPath)\n }\n\n // Recursively check nested fields\n if ('fields' in field && Array.isArray(field.fields)) {\n paths.push(...getLocalizedFieldPaths(field.fields, fieldPath))\n }\n\n // Check blocks\n if (field.type === 'blocks' && 'blocks' in field && Array.isArray(field.blocks)) {\n for (const block of field.blocks) {\n if ('fields' in block && Array.isArray(block.fields)) {\n paths.push(...getLocalizedFieldPaths(block.fields, fieldPath))\n }\n }\n }\n\n // Check group fields\n if (field.type === 'group' && 'fields' in field && Array.isArray(field.fields)) {\n paths.push(...getLocalizedFieldPaths(field.fields, fieldPath))\n }\n\n // Check array fields\n if (field.type === 'array' && 'fields' in field && Array.isArray(field.fields)) {\n paths.push(...getLocalizedFieldPaths(field.fields, fieldPath))\n }\n }\n\n return paths\n}\n"],"names":["extractFieldPaths","data","parentPath","fields","paths","key","value","Object","entries","currentPath","push","path","Array","isArray","forEach","item","index","arrayPath","filterExcludedPaths","excludedPaths","length","filtered","JSON","parse","stringify","excludedPath","deletePath","mergeTranslatedData","originalData","translatedData","merged","merge","target","source","fullPath","isPathExcluded","some","startsWith","pathParts","split","excludedParts","i","Math","min","isNaN","Number","obj","parts","current","getValueAtPath","reduce","part","setValueAtPath","isLocalizedField","field","localized","getLocalizedFieldPaths","fieldPath","name","type","blocks","block"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/fieldHelpers.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { FieldPath } from '../types/index.js'\n\n/**\n * Field types whose stored value is an option/enum constant rather than\n * human-readable prose. The Postgres adapter persists these as native `enum`\n * columns, so translating their values (e.g. \"narrow\" -> \"schmal\") produces an\n * invalid enum value and makes the locale-row INSERT fail with\n * `invalid input value for enum`. These must never be sent to the translator.\n */\nconst NON_TRANSLATABLE_FIELD_TYPES = new Set(['select', 'radio'])\n\n/**\n * Container field types that hold nested fields rather than a leaf value.\n * Localization (`localized: true`) set on a named container (`group`, `array`,\n * `blocks`, named `tab`) cascades to every field nested within it.\n */\nconst CONTAINER_FIELD_TYPES = new Set(['array', 'blocks', 'collapsible', 'group', 'row', 'tabs'])\n\nexport type OverlayOptions = {\n /**\n * Localization inherited from an ancestor container (`group`/`array`/`blocks`/\n * named `tab`) that has `localized: true`. When true, every nested field is\n * treated as localized.\n */\n inheritedLocalized?: boolean\n /**\n * When true, only fields that are localized (directly or via an ancestor\n * container) are kept from `translated`; every non-localized field is restored\n * from `original` so it is effectively excluded from translation.\n */\n localizedOnly?: boolean\n}\n\n/**\n * Walks a Payload field schema and copies canonical (untranslated) values from\n * `original` back into `translated`, in place. A field's value is restored when:\n *\n * - it is a `select`/`radio` field (enum-backed; always restored), or\n * - `localizedOnly` is enabled and the field is not localized (directly or via\n * a localized ancestor container).\n *\n * This works regardless of which translation strategy produced `translated`\n * (optimized, legacy, or a custom translator).\n */\nexport function overlayNonTranslatableValues(\n translated: any,\n original: any,\n fields: Field[] | undefined,\n options: OverlayOptions = {},\n): void {\n if (\n !Array.isArray(fields) ||\n !translated ||\n typeof translated !== 'object' ||\n !original ||\n typeof original !== 'object'\n ) {\n return\n }\n\n const { inheritedLocalized = false, localizedOnly = false } = options\n\n for (const field of fields as any[]) {\n const type = field?.type\n const name = typeof field?.name === 'string' ? field.name : undefined\n const isLocalized = inheritedLocalized || field?.localized === true\n\n // Container fields: recurse, cascading localization to nested fields.\n if (type && CONTAINER_FIELD_TYPES.has(type)) {\n const childOptions: OverlayOptions = { inheritedLocalized: isLocalized, localizedOnly }\n\n switch (type) {\n case 'array':\n if (name && Array.isArray(translated[name]) && Array.isArray(original[name])) {\n translated[name].forEach((item: any, index: number) => {\n overlayNonTranslatableValues(item, original[name][index], field.fields, childOptions)\n })\n } else if (localizedOnly && name && !isLocalized && original[name] !== undefined) {\n translated[name] = original[name]\n }\n break\n\n case 'blocks':\n if (name && Array.isArray(translated[name]) && Array.isArray(original[name])) {\n const blockDefs: any[] = Array.isArray(field.blocks) ? field.blocks : []\n translated[name].forEach((item: any, index: number) => {\n const originalItem = original[name][index]\n const blockType = item?.blockType ?? originalItem?.blockType\n const blockDef = blockDefs.find((b) => b?.slug === blockType)\n if (blockDef) {\n overlayNonTranslatableValues(item, originalItem, blockDef.fields, childOptions)\n }\n })\n } else if (localizedOnly && name && !isLocalized && original[name] !== undefined) {\n translated[name] = original[name]\n }\n break\n\n case 'collapsible':\n case 'row':\n // Presentational wrappers share the parent object and cannot be localized.\n overlayNonTranslatableValues(translated, original, field.fields, {\n inheritedLocalized,\n localizedOnly,\n })\n break\n\n case 'group':\n if (name) {\n overlayNonTranslatableValues(\n translated[name],\n original[name],\n field.fields,\n childOptions,\n )\n }\n break\n\n case 'tabs':\n if (Array.isArray(field.tabs)) {\n for (const tab of field.tabs) {\n const tabName = typeof tab?.name === 'string' ? tab.name : undefined\n const tabLocalized = inheritedLocalized || tab?.localized === true\n if (tabName) {\n overlayNonTranslatableValues(translated[tabName], original[tabName], tab.fields, {\n inheritedLocalized: tabLocalized,\n localizedOnly,\n })\n } else {\n // Unnamed tabs share the parent object.\n overlayNonTranslatableValues(translated, original, tab.fields, {\n inheritedLocalized,\n localizedOnly,\n })\n }\n }\n }\n break\n }\n\n continue\n }\n\n // Leaf fields: restore the original value when it must not be translated.\n if (!name) {\n continue\n }\n\n const isEnumField = Boolean(type && NON_TRANSLATABLE_FIELD_TYPES.has(type))\n const shouldRestore = isEnumField || (localizedOnly && !isLocalized)\n\n if (shouldRestore && original[name] !== undefined) {\n translated[name] = original[name]\n }\n }\n}\n\n/**\n * Recursively extracts all field paths and their values from a document\n */\nexport function extractFieldPaths(\n data: any,\n parentPath: string = '',\n fields?: Field[],\n): FieldPath[] {\n const paths: FieldPath[] = []\n\n if (!data || typeof data !== 'object') {\n return paths\n }\n\n for (const [key, value] of Object.entries(data)) {\n const currentPath = parentPath ? `${parentPath}.${key}` : key\n\n // Skip internal fields\n if (\n key === 'id' ||\n key === '_id' ||\n key === 'createdAt' ||\n key === 'updatedAt' ||\n key === 'translationSync' ||\n key === '__v'\n ) {\n continue\n }\n\n // Add current field\n paths.push({\n parentPath,\n path: currentPath,\n value,\n })\n\n // Recursively process nested objects\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n paths.push(...extractFieldPaths(value, currentPath, fields))\n }\n\n // Process arrays\n if (Array.isArray(value)) {\n value.forEach((item, index) => {\n const arrayPath = `${currentPath}.${index}`\n if (item && typeof item === 'object') {\n paths.push(...extractFieldPaths(item, arrayPath, fields))\n }\n })\n }\n }\n\n return paths\n}\n\n/**\n * Filters out excluded paths from data before translation\n */\nexport function filterExcludedPaths(data: any, excludedPaths: string[]): any {\n if (!data || typeof data !== 'object' || excludedPaths.length === 0) {\n return data\n }\n\n const filtered = JSON.parse(JSON.stringify(data)) // Deep clone\n\n for (const excludedPath of excludedPaths) {\n deletePath(filtered, excludedPath)\n }\n\n return filtered\n}\n\n/**\n * Merges translated data back, respecting excluded paths\n */\nexport function mergeTranslatedData(\n originalData: any,\n translatedData: any,\n excludedPaths: string[],\n): any {\n if (!translatedData || typeof translatedData !== 'object') {\n return originalData\n }\n\n const merged = JSON.parse(JSON.stringify(originalData)) // Deep clone\n\n function merge(target: any, source: any, currentPath: string = ''): void {\n for (const key in source) {\n const fullPath = currentPath ? `${currentPath}.${key}` : key\n\n // Skip if this path is excluded\n if (isPathExcluded(fullPath, excludedPaths)) {\n continue\n }\n\n // Skip internal fields\n if (\n key === 'id' ||\n key === '_id' ||\n key === 'createdAt' ||\n key === 'updatedAt' ||\n key === 'translationSync' ||\n key === '__v'\n ) {\n continue\n }\n\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n if (!target[key]) {\n target[key] = {}\n }\n merge(target[key], source[key], fullPath)\n } else {\n target[key] = source[key]\n }\n }\n }\n\n merge(merged, translatedData)\n return merged\n}\n\n/**\n * Checks if a path is excluded or if any parent path is excluded\n */\nexport function isPathExcluded(path: string, excludedPaths: string[]): boolean {\n return excludedPaths.some((excludedPath) => {\n // Exact match\n if (path === excludedPath) {\n return true\n }\n\n // Check if path is a child of excluded path\n if (path.startsWith(`${excludedPath}.`)) {\n return true\n }\n\n // Check if excluded path is a pattern match for arrays (e.g., content.0.title)\n const pathParts = path.split('.')\n const excludedParts = excludedPath.split('.')\n\n for (let i = 0; i < Math.min(pathParts.length, excludedParts.length); i++) {\n if (excludedParts[i] !== pathParts[i]) {\n // Check if it's an array index difference\n if (!isNaN(Number(pathParts[i])) && !isNaN(Number(excludedParts[i]))) {\n continue\n }\n return false\n }\n }\n\n return excludedParts.length <= pathParts.length\n })\n}\n\n/**\n * Deletes a path from an object using dot notation\n */\nfunction deletePath(obj: any, path: string): void {\n const parts = path.split('.')\n let current = obj\n\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]]) {\n return\n }\n current = current[parts[i]]\n }\n\n delete current[parts[parts.length - 1]]\n}\n\n/**\n * Gets value at path using dot notation\n */\nexport function getValueAtPath(obj: any, path: string): any {\n return path.split('.').reduce((current, part) => current?.[part], obj)\n}\n\n/**\n * Sets value at path using dot notation\n */\nexport function setValueAtPath(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 if (!current[parts[i]]) {\n current[parts[i]] = {}\n }\n current = current[parts[i]]\n }\n\n current[parts[parts.length - 1]] = value\n}\n\n/**\n * Checks if a field is a localized field\n */\nexport function isLocalizedField(field: Field): boolean {\n return 'localized' in field && field.localized === true\n}\n\n/**\n * Gets all localized field paths from a collection config\n */\nexport function getLocalizedFieldPaths(fields: Field[], parentPath: string = ''): string[] {\n const paths: string[] = []\n\n for (const field of fields) {\n if (!('name' in field)) {\n continue\n }\n\n const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name\n\n if (isLocalizedField(field)) {\n paths.push(fieldPath)\n }\n\n // Recursively check nested fields\n if ('fields' in field && Array.isArray(field.fields)) {\n paths.push(...getLocalizedFieldPaths(field.fields, fieldPath))\n }\n\n // Check blocks\n if (field.type === 'blocks' && 'blocks' in field && Array.isArray(field.blocks)) {\n for (const block of field.blocks) {\n if ('fields' in block && Array.isArray(block.fields)) {\n paths.push(...getLocalizedFieldPaths(block.fields, fieldPath))\n }\n }\n }\n\n // Check group fields\n if (field.type === 'group' && 'fields' in field && Array.isArray(field.fields)) {\n paths.push(...getLocalizedFieldPaths(field.fields, fieldPath))\n }\n\n // Check array fields\n if (field.type === 'array' && 'fields' in field && Array.isArray(field.fields)) {\n paths.push(...getLocalizedFieldPaths(field.fields, fieldPath))\n }\n }\n\n return paths\n}\n"],"names":["NON_TRANSLATABLE_FIELD_TYPES","Set","CONTAINER_FIELD_TYPES","overlayNonTranslatableValues","translated","original","fields","options","Array","isArray","inheritedLocalized","localizedOnly","field","type","name","undefined","isLocalized","localized","has","childOptions","forEach","item","index","blockDefs","blocks","originalItem","blockType","blockDef","find","b","slug","tabs","tab","tabName","tabLocalized","isEnumField","Boolean","shouldRestore","extractFieldPaths","data","parentPath","paths","key","value","Object","entries","currentPath","push","path","arrayPath","filterExcludedPaths","excludedPaths","length","filtered","JSON","parse","stringify","excludedPath","deletePath","mergeTranslatedData","originalData","translatedData","merged","merge","target","source","fullPath","isPathExcluded","some","startsWith","pathParts","split","excludedParts","i","Math","min","isNaN","Number","obj","parts","current","getValueAtPath","reduce","part","setValueAtPath","isLocalizedField","getLocalizedFieldPaths","fieldPath","block"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pigment/auto-translate",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Automatic translation plugin for Payload CMS with field-level exclusion controls and performance optimizations",
5
5
  "keywords": [
6
6
  "payload",
@@ -27,18 +27,18 @@
27
27
  "type": "module",
28
28
  "exports": {
29
29
  ".": {
30
- "import": "./dist/index.js",
31
30
  "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
32
  "default": "./dist/index.js"
33
33
  },
34
34
  "./client": {
35
- "import": "./dist/exports/client.js",
36
35
  "types": "./dist/exports/client.d.ts",
36
+ "import": "./dist/exports/client.js",
37
37
  "default": "./dist/exports/client.js"
38
38
  },
39
39
  "./rsc": {
40
- "import": "./dist/exports/rsc.js",
41
40
  "types": "./dist/exports/rsc.d.ts",
41
+ "import": "./dist/exports/rsc.js",
42
42
  "default": "./dist/exports/rsc.js"
43
43
  }
44
44
  },
@@ -54,6 +54,7 @@
54
54
  "@payloadcms/db-sqlite": "3.85.0",
55
55
  "@payloadcms/eslint-config": "3.28.0",
56
56
  "@payloadcms/next": "3.85.0",
57
+ "@payloadcms/plugin-nested-docs": "3.85.0",
57
58
  "@payloadcms/richtext-lexical": "3.85.0",
58
59
  "@payloadcms/ui": "3.85.0",
59
60
  "@playwright/test": "^1.52.0",