@pigment/auto-translate 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +28 -10
  2. package/dist/collections/translationExclusions.js +101 -4
  3. package/dist/collections/translationExclusions.js.map +1 -1
  4. package/dist/components/LockTranslation/actions/lockTranslations.d.ts +1 -1
  5. package/dist/components/LockTranslation/actions/lockTranslations.js +2 -2
  6. package/dist/components/LockTranslation/actions/lockTranslations.js.map +1 -1
  7. package/dist/components/LockTranslation/index.js +7 -6
  8. package/dist/components/LockTranslation/index.js.map +1 -1
  9. package/dist/components/OpenAiModelField.d.ts +2 -0
  10. package/dist/components/OpenAiModelField.js +96 -0
  11. package/dist/components/OpenAiModelField.js.map +1 -0
  12. package/dist/components/TranslationControl.js +45 -51
  13. package/dist/components/TranslationControl.js.map +1 -1
  14. package/dist/endpoints/listOpenAiModels.d.ts +5 -0
  15. package/dist/endpoints/listOpenAiModels.js +93 -0
  16. package/dist/endpoints/listOpenAiModels.js.map +1 -0
  17. package/dist/exports/client.d.ts +1 -0
  18. package/dist/exports/client.js +1 -0
  19. package/dist/exports/client.js.map +1 -1
  20. package/dist/globals/translationSettings.js +24 -13
  21. package/dist/globals/translationSettings.js.map +1 -1
  22. package/dist/services/translationService.js +17 -17
  23. package/dist/services/translationService.js.map +1 -1
  24. package/dist/utilities/fieldHelpers.d.ts +0 -29
  25. package/dist/utilities/fieldHelpers.js +0 -152
  26. package/dist/utilities/fieldHelpers.js.map +1 -1
  27. package/dist/utilities/injectTranslationControls.d.ts +1 -1
  28. package/dist/utilities/injectTranslationControls.js +16 -11
  29. package/dist/utilities/injectTranslationControls.js.map +1 -1
  30. package/package.json +12 -12
  31. package/dist/components/TranslationSettingsLock.css +0 -87
  32. package/dist/components/TranslationSettingsLock.d.ts +0 -9
  33. package/dist/components/TranslationSettingsLock.js +0 -155
  34. package/dist/components/TranslationSettingsLock.js.map +0 -1
@@ -1 +1 @@
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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"}
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 { supportsCustomTemperature } from '../endpoints/listOpenAiModels.js'\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 = {\n messages: [\n {\n content: systemMessage,\n role: 'system' as const,\n },\n {\n content: JSON.stringify(data, null, 2),\n role: 'user' as const,\n },\n ],\n model: settings.model,\n response_format: { type: 'json_object' as const },\n ...(supportsCustomTemperature(settings.model)\n ? { temperature: settings.temperature }\n : {}),\n ...(settings.maxTokens ? { 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 payload.logger.error(`[Auto-Translate] Error fetching exclusions: ${error}`)\n throw error\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 = {\n messages: [\n {\n content: systemMessage,\n role: 'system' as const,\n },\n {\n content: JSON.stringify(stringsToTranslate, null, 2),\n role: 'user' as const,\n },\n ],\n model: settings.model,\n response_format: { type: 'json_object' as const },\n ...(supportsCustomTemperature(settings.model)\n ? { temperature: settings.temperature }\n : {}),\n ...(settings.maxTokens ? { 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 payload.logger.error(`[Auto-Translate] Error updating exclusions: ${error}`)\n throw error\n }\n }\n}\n"],"names":["OpenAI","supportsCustomTemperature","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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"}
@@ -1,5 +1,4 @@
1
1
  import type { Field } from 'payload';
2
- import type { FieldPath } from '../types/index.js';
3
2
  export type OverlayOptions = {
4
3
  /**
5
4
  * Localization inherited from an ancestor container (`group`/`array`/`blocks`/
@@ -26,35 +25,7 @@ export type OverlayOptions = {
26
25
  * (optimized, legacy, or a custom translator).
27
26
  */
28
27
  export declare function overlayNonTranslatableValues(translated: any, original: any, fields: Field[] | undefined, options?: OverlayOptions): void;
29
- /**
30
- * Recursively extracts all field paths and their values from a document
31
- */
32
- export declare function extractFieldPaths(data: any, parentPath?: string, fields?: Field[]): FieldPath[];
33
28
  /**
34
29
  * Filters out excluded paths from data before translation
35
30
  */
36
31
  export declare function filterExcludedPaths(data: any, excludedPaths: string[]): any;
37
- /**
38
- * Merges translated data back, respecting excluded paths
39
- */
40
- export declare function mergeTranslatedData(originalData: any, translatedData: any, excludedPaths: string[]): any;
41
- /**
42
- * Checks if a path is excluded or if any parent path is excluded
43
- */
44
- export declare function isPathExcluded(path: string, excludedPaths: string[]): boolean;
45
- /**
46
- * Gets value at path using dot notation
47
- */
48
- export declare function getValueAtPath(obj: any, path: string): any;
49
- /**
50
- * Sets value at path using dot notation
51
- */
52
- export declare function setValueAtPath(obj: any, path: string, value: any): void;
53
- /**
54
- * Checks if a field is a localized field
55
- */
56
- export declare function isLocalizedField(field: Field): boolean;
57
- /**
58
- * Gets all localized field paths from a collection config
59
- */
60
- export declare function getLocalizedFieldPaths(fields: Field[], parentPath?: string): string[];
@@ -117,41 +117,6 @@
117
117
  }
118
118
  }
119
119
  }
120
- /**
121
- * Recursively extracts all field paths and their values from a document
122
- */ export function extractFieldPaths(data, parentPath = '', fields) {
123
- const paths = [];
124
- if (!data || typeof data !== 'object') {
125
- return paths;
126
- }
127
- for (const [key, value] of Object.entries(data)){
128
- const currentPath = parentPath ? `${parentPath}.${key}` : key;
129
- // Skip internal fields
130
- if (key === 'id' || key === '_id' || key === 'createdAt' || key === 'updatedAt' || key === 'translationSync' || key === '__v') {
131
- continue;
132
- }
133
- // Add current field
134
- paths.push({
135
- parentPath,
136
- path: currentPath,
137
- value
138
- });
139
- // Recursively process nested objects
140
- if (value && typeof value === 'object' && !Array.isArray(value)) {
141
- paths.push(...extractFieldPaths(value, currentPath, fields));
142
- }
143
- // Process arrays
144
- if (Array.isArray(value)) {
145
- value.forEach((item, index)=>{
146
- const arrayPath = `${currentPath}.${index}`;
147
- if (item && typeof item === 'object') {
148
- paths.push(...extractFieldPaths(item, arrayPath, fields));
149
- }
150
- });
151
- }
152
- }
153
- return paths;
154
- }
155
120
  /**
156
121
  * Filters out excluded paths from data before translation
157
122
  */ export function filterExcludedPaths(data, excludedPaths) {
@@ -165,65 +130,6 @@
165
130
  }
166
131
  return filtered;
167
132
  }
168
- /**
169
- * Merges translated data back, respecting excluded paths
170
- */ export function mergeTranslatedData(originalData, translatedData, excludedPaths) {
171
- if (!translatedData || typeof translatedData !== 'object') {
172
- return originalData;
173
- }
174
- const merged = JSON.parse(JSON.stringify(originalData)) // Deep clone
175
- ;
176
- function merge(target, source, currentPath = '') {
177
- for(const key in source){
178
- const fullPath = currentPath ? `${currentPath}.${key}` : key;
179
- // Skip if this path is excluded
180
- if (isPathExcluded(fullPath, excludedPaths)) {
181
- continue;
182
- }
183
- // Skip internal fields
184
- if (key === 'id' || key === '_id' || key === 'createdAt' || key === 'updatedAt' || key === 'translationSync' || key === '__v') {
185
- continue;
186
- }
187
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
188
- if (!target[key]) {
189
- target[key] = {};
190
- }
191
- merge(target[key], source[key], fullPath);
192
- } else {
193
- target[key] = source[key];
194
- }
195
- }
196
- }
197
- merge(merged, translatedData);
198
- return merged;
199
- }
200
- /**
201
- * Checks if a path is excluded or if any parent path is excluded
202
- */ export function isPathExcluded(path, excludedPaths) {
203
- return excludedPaths.some((excludedPath)=>{
204
- // Exact match
205
- if (path === excludedPath) {
206
- return true;
207
- }
208
- // Check if path is a child of excluded path
209
- if (path.startsWith(`${excludedPath}.`)) {
210
- return true;
211
- }
212
- // Check if excluded path is a pattern match for arrays (e.g., content.0.title)
213
- const pathParts = path.split('.');
214
- const excludedParts = excludedPath.split('.');
215
- for(let i = 0; i < Math.min(pathParts.length, excludedParts.length); i++){
216
- if (excludedParts[i] !== pathParts[i]) {
217
- // Check if it's an array index difference
218
- if (!isNaN(Number(pathParts[i])) && !isNaN(Number(excludedParts[i]))) {
219
- continue;
220
- }
221
- return false;
222
- }
223
- }
224
- return excludedParts.length <= pathParts.length;
225
- });
226
- }
227
133
  /**
228
134
  * Deletes a path from an object using dot notation
229
135
  */ function deletePath(obj, path) {
@@ -237,63 +143,5 @@
237
143
  }
238
144
  delete current[parts[parts.length - 1]];
239
145
  }
240
- /**
241
- * Gets value at path using dot notation
242
- */ export function getValueAtPath(obj, path) {
243
- return path.split('.').reduce((current, part)=>current?.[part], obj);
244
- }
245
- /**
246
- * Sets value at path using dot notation
247
- */ export function setValueAtPath(obj, path, value) {
248
- const parts = path.split('.');
249
- let current = obj;
250
- for(let i = 0; i < parts.length - 1; i++){
251
- if (!current[parts[i]]) {
252
- current[parts[i]] = {};
253
- }
254
- current = current[parts[i]];
255
- }
256
- current[parts[parts.length - 1]] = value;
257
- }
258
- /**
259
- * Checks if a field is a localized field
260
- */ export function isLocalizedField(field) {
261
- return 'localized' in field && field.localized === true;
262
- }
263
- /**
264
- * Gets all localized field paths from a collection config
265
- */ export function getLocalizedFieldPaths(fields, parentPath = '') {
266
- const paths = [];
267
- for (const field of fields){
268
- if (!('name' in field)) {
269
- continue;
270
- }
271
- const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
272
- if (isLocalizedField(field)) {
273
- paths.push(fieldPath);
274
- }
275
- // Recursively check nested fields
276
- if ('fields' in field && Array.isArray(field.fields)) {
277
- paths.push(...getLocalizedFieldPaths(field.fields, fieldPath));
278
- }
279
- // Check blocks
280
- if (field.type === 'blocks' && 'blocks' in field && Array.isArray(field.blocks)) {
281
- for (const block of field.blocks){
282
- if ('fields' in block && Array.isArray(block.fields)) {
283
- paths.push(...getLocalizedFieldPaths(block.fields, fieldPath));
284
- }
285
- }
286
- }
287
- // Check group fields
288
- if (field.type === 'group' && 'fields' in field && Array.isArray(field.fields)) {
289
- paths.push(...getLocalizedFieldPaths(field.fields, fieldPath));
290
- }
291
- // Check array fields
292
- if (field.type === 'array' && 'fields' in field && Array.isArray(field.fields)) {
293
- paths.push(...getLocalizedFieldPaths(field.fields, fieldPath));
294
- }
295
- }
296
- return paths;
297
- }
298
146
 
299
147
  //# sourceMappingURL=fieldHelpers.js.map
@@ -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 * 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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"}
1
+ {"version":3,"sources":["../../src/utilities/fieldHelpers.ts"],"sourcesContent":["import type { Field } from 'payload'\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 * 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 * 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"],"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","filterExcludedPaths","data","excludedPaths","length","filtered","JSON","parse","stringify","excludedPath","deletePath","obj","path","parts","split","current","i"],"mappings":"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"}
@@ -2,4 +2,4 @@ import type { Field } from 'payload';
2
2
  /**
3
3
  * Recursively injects TranslationControl component into all localized fields
4
4
  */
5
- export declare function injectTranslationControls(fields: Field[], defaultLocale: string, parentPath?: string): Field[];
5
+ export declare function injectTranslationControls(fields: Field[], defaultLocale: string, parentPath?: string, inheritedLocalized?: boolean): Field[];
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Recursively injects TranslationControl component into all localized fields
3
- */ export function injectTranslationControls(fields, defaultLocale, parentPath = '') {
3
+ */ export function injectTranslationControls(fields, defaultLocale, parentPath = '', // Localization inherited from an ancestor container (group/array/blocks/
4
+ // named tab) that has `localized: true`. Payload cascades localization to
5
+ // every nested field, so a field can be localized without setting the flag
6
+ // itself.
7
+ inheritedLocalized = false) {
4
8
  return fields.map((field)=>{
5
- // Skip fields without names
6
- if (!('name' in field)) {
7
- return field;
8
- }
9
- const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
9
+ // Fields without names (e.g. `row`, `collapsible`) don't add a path segment,
10
+ // but their nested fields still need to be walked below.
11
+ const hasName = 'name' in field;
12
+ const fieldPath = hasName ? parentPath ? `${parentPath}.${field.name}` : field.name : parentPath;
10
13
  // Clone the field to avoid mutations
11
14
  const clonedField = {
12
15
  ...field
@@ -19,7 +22,8 @@
19
22
  clonedField.type === 'array' || // Arrays are containers
20
23
  clonedField.type === 'tabs' // Tabs are UI containers
21
24
  ;
22
- if ('localized' in clonedField && clonedField.localized === true && !shouldSkipControl) {
25
+ const isLocalized = inheritedLocalized || clonedField.localized === true;
26
+ if (hasName && isLocalized && !shouldSkipControl) {
23
27
  // Initialize admin if not present
24
28
  if (!clonedField.admin) {
25
29
  clonedField.admin = {};
@@ -51,9 +55,9 @@
51
55
  });
52
56
  }
53
57
  }
54
- // Recursively inject into nested fields
58
+ // Recursively inject into nested fields, cascading localization to children
55
59
  if ('fields' in clonedField && Array.isArray(clonedField.fields)) {
56
- clonedField.fields = injectTranslationControls(clonedField.fields, defaultLocale, fieldPath);
60
+ clonedField.fields = injectTranslationControls(clonedField.fields, defaultLocale, fieldPath, isLocalized);
57
61
  }
58
62
  // Recursively inject into tabs
59
63
  // Note: Tabs fields themselves don't create a path segment
@@ -65,9 +69,10 @@
65
69
  // If the tab has a name, use it as the path segment
66
70
  // Otherwise, use the parent path (tabs field itself doesn't create a path)
67
71
  const tabPath = tab.name ? parentPath ? `${parentPath}.${tab.name}` : tab.name : parentPath;
72
+ const tabLocalized = isLocalized || tab.localized === true;
68
73
  return {
69
74
  ...tab,
70
- fields: injectTranslationControls(tab.fields, defaultLocale, tabPath)
75
+ fields: injectTranslationControls(tab.fields, defaultLocale, tabPath, tabLocalized)
71
76
  };
72
77
  }
73
78
  return tab;
@@ -79,7 +84,7 @@
79
84
  if (block.fields) {
80
85
  return {
81
86
  ...block,
82
- fields: injectTranslationControls(block.fields, defaultLocale, fieldPath)
87
+ fields: injectTranslationControls(block.fields, defaultLocale, fieldPath, isLocalized)
83
88
  };
84
89
  }
85
90
  return block;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/injectTranslationControls.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Recursively injects TranslationControl component into all localized fields\n */\nexport function injectTranslationControls(\n fields: Field[],\n defaultLocale: string,\n parentPath: string = '',\n): Field[] {\n return fields.map((field) => {\n // Skip fields without names\n if (!('name' in field)) {\n return field\n }\n\n const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name\n\n // Clone the field to avoid mutations\n const clonedField: any = { ...field }\n\n // Inject TranslationControl if the field is localized\n // Skip for container fields - they're just UI/structural, not actual data fields\n // Only their nested fields should have translation controls\n const shouldSkipControl =\n clonedField.type === 'group' || // Groups are containers\n clonedField.type === 'blocks' || // Blocks are containers\n clonedField.type === 'array' || // Arrays are containers\n clonedField.type === 'tabs' // Tabs are UI containers\n\n if ('localized' in clonedField && clonedField.localized === true && !shouldSkipControl) {\n // Initialize admin if not present\n if (!clonedField.admin) {\n clonedField.admin = {}\n }\n\n // Initialize components if not present\n if (!clonedField.admin.components) {\n clonedField.admin.components = {}\n }\n\n // Initialize afterInput if not present\n if (!clonedField.admin.components.afterInput) {\n clonedField.admin.components.afterInput = []\n }\n\n // Ensure afterInput is an array\n if (!Array.isArray(clonedField.admin.components.afterInput)) {\n clonedField.admin.components.afterInput = [clonedField.admin.components.afterInput]\n }\n\n // Check if TranslationControl is already added\n const hasTranslationControl = clonedField.admin.components.afterInput.some(\n (component: any) =>\n typeof component === 'object' &&\n component.path === '@pigment/auto-translate/client#TranslationControl',\n )\n\n // Add TranslationControl if not already present\n if (!hasTranslationControl) {\n clonedField.admin.components.afterInput.push({\n clientProps: {\n defaultLocale, // Pass default locale to component\n fieldPath,\n },\n path: '@pigment/auto-translate/client#TranslationControl',\n })\n }\n }\n\n // Recursively inject into nested fields\n if ('fields' in clonedField && Array.isArray(clonedField.fields)) {\n clonedField.fields = injectTranslationControls(clonedField.fields, defaultLocale, fieldPath)\n }\n\n // Recursively inject into tabs\n // Note: Tabs fields themselves don't create a path segment\n // Named tabs (with 'name' property) create their own path segment\n // Unnamed tabs use the parent path\n if ('tabs' in clonedField && Array.isArray(clonedField.tabs)) {\n clonedField.tabs = clonedField.tabs.map((tab: any) => {\n if (tab.fields) {\n // If the tab has a name, use it as the path segment\n // Otherwise, use the parent path (tabs field itself doesn't create a path)\n const tabPath = tab.name \n ? (parentPath ? `${parentPath}.${tab.name}` : tab.name)\n : parentPath\n \n return {\n ...tab,\n fields: injectTranslationControls(tab.fields, defaultLocale, tabPath),\n }\n }\n return tab\n })\n }\n\n // Recursively inject into blocks\n if (clonedField.type === 'blocks' && 'blocks' in clonedField) {\n clonedField.blocks = clonedField.blocks.map((block: any) => {\n if (block.fields) {\n return {\n ...block,\n fields: injectTranslationControls(block.fields, defaultLocale, fieldPath),\n }\n }\n return block\n })\n }\n\n return clonedField as Field\n })\n}\n"],"names":["injectTranslationControls","fields","defaultLocale","parentPath","map","field","fieldPath","name","clonedField","shouldSkipControl","type","localized","admin","components","afterInput","Array","isArray","hasTranslationControl","some","component","path","push","clientProps","tabs","tab","tabPath","blocks","block"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/injectTranslationControls.ts"],"sourcesContent":["import type { Field } from 'payload'\n\n/**\n * Recursively injects TranslationControl component into all localized fields\n */\nexport function injectTranslationControls(\n fields: Field[],\n defaultLocale: string,\n parentPath: string = '',\n // Localization inherited from an ancestor container (group/array/blocks/\n // named tab) that has `localized: true`. Payload cascades localization to\n // every nested field, so a field can be localized without setting the flag\n // itself.\n inheritedLocalized: boolean = false,\n): Field[] {\n return fields.map((field) => {\n // Fields without names (e.g. `row`, `collapsible`) don't add a path segment,\n // but their nested fields still need to be walked below.\n const hasName = 'name' in field\n const fieldPath = hasName ? (parentPath ? `${parentPath}.${field.name}` : field.name) : parentPath\n\n // Clone the field to avoid mutations\n const clonedField: any = { ...field }\n\n // Inject TranslationControl if the field is localized\n // Skip for container fields - they're just UI/structural, not actual data fields\n // Only their nested fields should have translation controls\n const shouldSkipControl =\n clonedField.type === 'group' || // Groups are containers\n clonedField.type === 'blocks' || // Blocks are containers\n clonedField.type === 'array' || // Arrays are containers\n clonedField.type === 'tabs' // Tabs are UI containers\n\n const isLocalized = inheritedLocalized || clonedField.localized === true\n\n if (hasName && isLocalized && !shouldSkipControl) {\n // Initialize admin if not present\n if (!clonedField.admin) {\n clonedField.admin = {}\n }\n\n // Initialize components if not present\n if (!clonedField.admin.components) {\n clonedField.admin.components = {}\n }\n\n // Initialize afterInput if not present\n if (!clonedField.admin.components.afterInput) {\n clonedField.admin.components.afterInput = []\n }\n\n // Ensure afterInput is an array\n if (!Array.isArray(clonedField.admin.components.afterInput)) {\n clonedField.admin.components.afterInput = [clonedField.admin.components.afterInput]\n }\n\n // Check if TranslationControl is already added\n const hasTranslationControl = clonedField.admin.components.afterInput.some(\n (component: any) =>\n typeof component === 'object' &&\n component.path === '@pigment/auto-translate/client#TranslationControl',\n )\n\n // Add TranslationControl if not already present\n if (!hasTranslationControl) {\n clonedField.admin.components.afterInput.push({\n clientProps: {\n defaultLocale, // Pass default locale to component\n fieldPath,\n },\n path: '@pigment/auto-translate/client#TranslationControl',\n })\n }\n }\n\n // Recursively inject into nested fields, cascading localization to children\n if ('fields' in clonedField && Array.isArray(clonedField.fields)) {\n clonedField.fields = injectTranslationControls(\n clonedField.fields,\n defaultLocale,\n fieldPath,\n isLocalized,\n )\n }\n\n // Recursively inject into tabs\n // Note: Tabs fields themselves don't create a path segment\n // Named tabs (with 'name' property) create their own path segment\n // Unnamed tabs use the parent path\n if ('tabs' in clonedField && Array.isArray(clonedField.tabs)) {\n clonedField.tabs = clonedField.tabs.map((tab: any) => {\n if (tab.fields) {\n // If the tab has a name, use it as the path segment\n // Otherwise, use the parent path (tabs field itself doesn't create a path)\n const tabPath = tab.name\n ? (parentPath ? `${parentPath}.${tab.name}` : tab.name)\n : parentPath\n\n const tabLocalized = isLocalized || tab.localized === true\n\n return {\n ...tab,\n fields: injectTranslationControls(tab.fields, defaultLocale, tabPath, tabLocalized),\n }\n }\n return tab\n })\n }\n\n // Recursively inject into blocks\n if (clonedField.type === 'blocks' && 'blocks' in clonedField) {\n clonedField.blocks = clonedField.blocks.map((block: any) => {\n if (block.fields) {\n return {\n ...block,\n fields: injectTranslationControls(block.fields, defaultLocale, fieldPath, isLocalized),\n }\n }\n return block\n })\n }\n\n return clonedField as Field\n })\n}\n"],"names":["injectTranslationControls","fields","defaultLocale","parentPath","inheritedLocalized","map","field","hasName","fieldPath","name","clonedField","shouldSkipControl","type","isLocalized","localized","admin","components","afterInput","Array","isArray","hasTranslationControl","some","component","path","push","clientProps","tabs","tab","tabPath","tabLocalized","blocks","block"],"mappings":"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"}