payload-translate 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,7 +19,7 @@ import { payloadTranslate } from 'payload-translate'
19
19
  export default buildConfig({
20
20
  plugins: [
21
21
  payloadTranslate({
22
- apiKey: process.env.GEMINI_API_KEY,
22
+ apiKey: process.env.GEMINI_API_KEY!,
23
23
  collections: ['posts', 'pages'],
24
24
  }),
25
25
  ],
@@ -91,8 +91,8 @@ export const translateHandler = async (req)=>{
91
91
  });
92
92
  // Apply translations to document
93
93
  const updatedData = applyTranslations(document, translatableFields, translations);
94
- // Remove immutable system fields before update
95
- const { id, createdAt, updatedAt, ...dataToUpdate } = updatedData;
94
+ // Remove all system/immutable fields recursively
95
+ const dataToUpdate = removeSystemFields(updatedData);
96
96
  // Update document in target locale
97
97
  await payload.update({
98
98
  id: documentId,
@@ -117,5 +117,31 @@ export const translateHandler = async (req)=>{
117
117
  });
118
118
  }
119
119
  };
120
+ /**
121
+ * Recursively removes system fields (id, createdAt, updatedAt) from an object
122
+ */ function removeSystemFields(obj) {
123
+ const result = {};
124
+ for (const [key, value] of Object.entries(obj)){
125
+ // Skip system fields at any level
126
+ if (key === 'id' || key === 'createdAt' || key === 'updatedAt') {
127
+ continue;
128
+ }
129
+ if (Array.isArray(value)) {
130
+ // Process array items
131
+ result[key] = value.map((item)=>{
132
+ if (item && typeof item === 'object' && !Array.isArray(item)) {
133
+ return removeSystemFields(item);
134
+ }
135
+ return item;
136
+ });
137
+ } else if (value && typeof value === 'object') {
138
+ // Recursively process nested objects
139
+ result[key] = removeSystemFields(value);
140
+ } else {
141
+ result[key] = value;
142
+ }
143
+ }
144
+ return result;
145
+ }
120
146
 
121
147
  //# sourceMappingURL=translateHandler.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/translateHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\n\nimport type { TranslateRequestBody, TranslateResponse } from '../types.js'\n\nimport { translateWithGemini } from '../services/gemini.js'\nimport { applyTranslations } from '../utils/applyTranslations.js'\nimport { extractTranslatableFields } from '../utils/extractTranslatableFields.js'\n\nexport const translateHandler: PayloadHandler = async (req) => {\n try {\n const { payload, user } = req\n\n // Check authentication\n if (!user) {\n return Response.json({ error: 'Unauthorized', success: false } as TranslateResponse, {\n status: 401,\n })\n }\n\n // Parse request body\n const body = (await req.json?.()) as TranslateRequestBody | undefined\n if (!body) {\n return Response.json(\n { error: 'Invalid request body', success: false } as TranslateResponse,\n { status: 400 },\n )\n }\n const { collection, documentId, sourceLocale, targetLocales } = body\n\n // Validate request\n if (!collection || !documentId || !sourceLocale || !targetLocales || targetLocales.length === 0) {\n return Response.json(\n { error: 'Missing required fields', success: false } as TranslateResponse,\n { status: 400 },\n )\n }\n\n // Get API key from config\n const apiKey = (payload.config.custom as Record<string, unknown>)?.translateApiKey as\n | string\n | undefined\n if (!apiKey) {\n return Response.json(\n { error: 'Translation API key not configured', success: false } as TranslateResponse,\n { status: 500 },\n )\n }\n\n // Fetch document in source locale\n const document = await payload.findByID({\n id: documentId,\n collection,\n depth: 0,\n locale: sourceLocale,\n })\n\n if (!document) {\n return Response.json({ error: 'Document not found', success: false } as TranslateResponse, {\n status: 404,\n })\n }\n\n // Get collection config to find localized fields\n const collectionConfig = payload.collections[collection]?.config\n if (!collectionConfig) {\n return Response.json({ error: 'Collection not found', success: false } as TranslateResponse, {\n status: 404,\n })\n }\n\n // Extract translatable fields\n const translatableFields = extractTranslatableFields(\n document as Record<string, unknown>,\n collectionConfig.fields,\n )\n\n if (translatableFields.length === 0) {\n return Response.json({\n message: 'No translatable fields found',\n success: true,\n translatedFields: 0,\n translatedLocales: 0,\n } as TranslateResponse)\n }\n\n // Prepare texts for translation\n const texts = translatableFields.map((f) => f.value)\n\n // Translate to each target locale\n for (const targetLocale of targetLocales) {\n // Translate with Gemini\n const translations = await translateWithGemini({\n apiKey,\n sourceLocale,\n targetLocale,\n texts,\n })\n\n // Apply translations to document\n const updatedData = applyTranslations(\n document as Record<string, unknown>,\n translatableFields,\n translations,\n )\n\n // Remove immutable system fields before update\n const { id, createdAt, updatedAt, ...dataToUpdate } = updatedData\n\n // Update document in target locale\n await payload.update({\n id: documentId,\n collection,\n data: dataToUpdate,\n locale: targetLocale,\n })\n }\n\n return Response.json({\n message: `Successfully translated ${translatableFields.length} field(s) to ${targetLocales.length} locale(s)`,\n success: true,\n translatedFields: translatableFields.length,\n translatedLocales: targetLocales.length,\n } as TranslateResponse)\n } catch (error) {\n console.error('Translation error:', error)\n return Response.json(\n {\n error: error instanceof Error ? error.message : 'Translation failed',\n success: false,\n } as TranslateResponse,\n { status: 500 },\n )\n }\n}\n"],"names":["translateWithGemini","applyTranslations","extractTranslatableFields","translateHandler","req","payload","user","Response","json","error","success","status","body","collection","documentId","sourceLocale","targetLocales","length","apiKey","config","custom","translateApiKey","document","findByID","id","depth","locale","collectionConfig","collections","translatableFields","fields","message","translatedFields","translatedLocales","texts","map","f","value","targetLocale","translations","updatedData","createdAt","updatedAt","dataToUpdate","update","data","console","Error"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/endpoints/translateHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\n\nimport type { TranslateRequestBody, TranslateResponse } from '../types.js'\n\nimport { translateWithGemini } from '../services/gemini.js'\nimport { applyTranslations } from '../utils/applyTranslations.js'\nimport { extractTranslatableFields } from '../utils/extractTranslatableFields.js'\n\nexport const translateHandler: PayloadHandler = async (req) => {\n try {\n const { payload, user } = req\n\n // Check authentication\n if (!user) {\n return Response.json({ error: 'Unauthorized', success: false } as TranslateResponse, {\n status: 401,\n })\n }\n\n // Parse request body\n const body = (await req.json?.()) as TranslateRequestBody | undefined\n if (!body) {\n return Response.json(\n { error: 'Invalid request body', success: false } as TranslateResponse,\n { status: 400 },\n )\n }\n const { collection, documentId, sourceLocale, targetLocales } = body\n\n // Validate request\n if (!collection || !documentId || !sourceLocale || !targetLocales || targetLocales.length === 0) {\n return Response.json(\n { error: 'Missing required fields', success: false } as TranslateResponse,\n { status: 400 },\n )\n }\n\n // Get API key from config\n const apiKey = (payload.config.custom as Record<string, unknown>)?.translateApiKey as\n | string\n | undefined\n if (!apiKey) {\n return Response.json(\n { error: 'Translation API key not configured', success: false } as TranslateResponse,\n { status: 500 },\n )\n }\n\n // Fetch document in source locale\n const document = await payload.findByID({\n id: documentId,\n collection,\n depth: 0,\n locale: sourceLocale,\n })\n\n if (!document) {\n return Response.json({ error: 'Document not found', success: false } as TranslateResponse, {\n status: 404,\n })\n }\n\n // Get collection config to find localized fields\n const collectionConfig = payload.collections[collection]?.config\n if (!collectionConfig) {\n return Response.json({ error: 'Collection not found', success: false } as TranslateResponse, {\n status: 404,\n })\n }\n\n // Extract translatable fields\n const translatableFields = extractTranslatableFields(\n document as Record<string, unknown>,\n collectionConfig.fields,\n )\n\n if (translatableFields.length === 0) {\n return Response.json({\n message: 'No translatable fields found',\n success: true,\n translatedFields: 0,\n translatedLocales: 0,\n } as TranslateResponse)\n }\n\n // Prepare texts for translation\n const texts = translatableFields.map((f) => f.value)\n\n // Translate to each target locale\n for (const targetLocale of targetLocales) {\n // Translate with Gemini\n const translations = await translateWithGemini({\n apiKey,\n sourceLocale,\n targetLocale,\n texts,\n })\n\n // Apply translations to document\n const updatedData = applyTranslations(\n document as Record<string, unknown>,\n translatableFields,\n translations,\n )\n\n // Remove all system/immutable fields recursively\n const dataToUpdate = removeSystemFields(updatedData)\n\n // Update document in target locale\n await payload.update({\n id: documentId,\n collection,\n data: dataToUpdate,\n locale: targetLocale,\n })\n }\n\n return Response.json({\n message: `Successfully translated ${translatableFields.length} field(s) to ${targetLocales.length} locale(s)`,\n success: true,\n translatedFields: translatableFields.length,\n translatedLocales: targetLocales.length,\n } as TranslateResponse)\n } catch (error) {\n console.error('Translation error:', error)\n return Response.json(\n {\n error: error instanceof Error ? error.message : 'Translation failed',\n success: false,\n } as TranslateResponse,\n { status: 500 },\n )\n }\n}\n\n/**\n * Recursively removes system fields (id, createdAt, updatedAt) from an object\n */\nfunction removeSystemFields(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(obj)) {\n // Skip system fields at any level\n if (key === 'id' || key === 'createdAt' || key === 'updatedAt') {\n continue\n }\n\n if (Array.isArray(value)) {\n // Process array items\n result[key] = value.map((item) => {\n if (item && typeof item === 'object' && !Array.isArray(item)) {\n return removeSystemFields(item as Record<string, unknown>)\n }\n return item\n })\n } else if (value && typeof value === 'object') {\n // Recursively process nested objects\n result[key] = removeSystemFields(value as Record<string, unknown>)\n } else {\n result[key] = value\n }\n }\n\n return result\n}\n"],"names":["translateWithGemini","applyTranslations","extractTranslatableFields","translateHandler","req","payload","user","Response","json","error","success","status","body","collection","documentId","sourceLocale","targetLocales","length","apiKey","config","custom","translateApiKey","document","findByID","id","depth","locale","collectionConfig","collections","translatableFields","fields","message","translatedFields","translatedLocales","texts","map","f","value","targetLocale","translations","updatedData","dataToUpdate","removeSystemFields","update","data","console","Error","obj","result","key","Object","entries","Array","isArray","item"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent';
1
+ const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent';
2
2
  export async function translateWithGemini({ apiKey, sourceLocale, targetLocale, texts }) {
3
3
  if (texts.length === 0) {
4
4
  return [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/gemini.ts"],"sourcesContent":["import type { GeminiTranslationRequest } from '../types.js'\n\ntype TranslateWithGeminiArgs = {\n apiKey: string\n} & GeminiTranslationRequest\n\nconst GEMINI_API_URL =\n 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent'\n\nexport async function translateWithGemini({\n apiKey,\n sourceLocale,\n targetLocale,\n texts,\n}: TranslateWithGeminiArgs): Promise<string[]> {\n if (texts.length === 0) {\n return []\n }\n\n const prompt = buildTranslationPrompt(texts, sourceLocale, targetLocale)\n\n const response = await fetch(`${GEMINI_API_URL}?key=${apiKey}`, {\n body: JSON.stringify({\n contents: [\n {\n parts: [{ text: prompt }],\n },\n ],\n generationConfig: {\n maxOutputTokens: 8192,\n temperature: 0.1,\n thinkingConfig: {\n thinkingBudget: 0,\n },\n topP: 0.95,\n },\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n const error = await response.text()\n throw new Error(`Gemini API error: ${error}`)\n }\n\n const result = await response.json()\n\n // Handle response - find the text part\n const parts = result.candidates?.[0]?.content?.parts || []\n const textPart = parts.find((p: { text?: string }) => p.text !== undefined)\n const generatedText = textPart?.text\n\n if (!generatedText) {\n throw new Error(`No translation returned from Gemini. Response: ${JSON.stringify(result)}`)\n }\n\n return parseTranslationResponse(generatedText, texts.length)\n}\n\nfunction buildTranslationPrompt(\n texts: string[],\n sourceLocale: string,\n targetLocale: string,\n): string {\n const textsJson = JSON.stringify(texts)\n\n return `You are a professional translator. Translate the following texts from ${sourceLocale} to ${targetLocale}.\n\nIMPORTANT RULES:\n1. Maintain the exact same formatting, including HTML tags, markdown, and special characters\n2. Do not translate proper nouns, brand names, or code/technical terms unless they have standard translations\n3. Preserve any placeholder variables like {{name}} or {0}\n4. Return ONLY a valid JSON array with the translations in the same order\n5. Each translation should correspond to the input at the same index\n\nInput texts (JSON array):\n${textsJson}\n\nReturn ONLY the JSON array of translations, nothing else. Example format:\n[\"translated text 1\", \"translated text 2\"]`\n}\n\nfunction parseTranslationResponse(response: string, expectedCount: number): string[] {\n let jsonStr = response.trim()\n\n // Handle code blocks if present\n if (jsonStr.startsWith('```')) {\n jsonStr = jsonStr.replace(/```json?\\n?/g, '').replace(/```/g, '').trim()\n }\n\n try {\n const translations = JSON.parse(jsonStr)\n\n if (!Array.isArray(translations)) {\n throw new Error('Response is not an array')\n }\n\n if (translations.length !== expectedCount) {\n console.warn(`Expected ${expectedCount} translations, got ${translations.length}`)\n }\n\n return translations\n } catch (_error) {\n console.error('Failed to parse translation response:', response)\n throw new Error('Failed to parse translation response from Gemini')\n }\n}\n"],"names":["GEMINI_API_URL","translateWithGemini","apiKey","sourceLocale","targetLocale","texts","length","prompt","buildTranslationPrompt","response","fetch","body","JSON","stringify","contents","parts","text","generationConfig","maxOutputTokens","temperature","thinkingConfig","thinkingBudget","topP","headers","method","ok","error","Error","result","json","candidates","content","textPart","find","p","undefined","generatedText","parseTranslationResponse","textsJson","expectedCount","jsonStr","trim","startsWith","replace","translations","parse","Array","isArray","console","warn","_error"],"mappings":"AAMA,MAAMA,iBACJ;AAEF,OAAO,eAAeC,oBAAoB,EACxCC,MAAM,EACNC,YAAY,EACZC,YAAY,EACZC,KAAK,EACmB;IACxB,IAAIA,MAAMC,MAAM,KAAK,GAAG;QACtB,OAAO,EAAE;IACX;IAEA,MAAMC,SAASC,uBAAuBH,OAAOF,cAAcC;IAE3D,MAAMK,WAAW,MAAMC,MAAM,GAAGV,eAAe,KAAK,EAAEE,QAAQ,EAAE;QAC9DS,MAAMC,KAAKC,SAAS,CAAC;YACnBC,UAAU;gBACR;oBACEC,OAAO;wBAAC;4BAAEC,MAAMT;wBAAO;qBAAE;gBAC3B;aACD;YACDU,kBAAkB;gBAChBC,iBAAiB;gBACjBC,aAAa;gBACbC,gBAAgB;oBACdC,gBAAgB;gBAClB;gBACAC,MAAM;YACR;QACF;QACAC,SAAS;YACP,gBAAgB;QAClB;QACAC,QAAQ;IACV;IAEA,IAAI,CAACf,SAASgB,EAAE,EAAE;QAChB,MAAMC,QAAQ,MAAMjB,SAASO,IAAI;QACjC,MAAM,IAAIW,MAAM,CAAC,kBAAkB,EAAED,OAAO;IAC9C;IAEA,MAAME,SAAS,MAAMnB,SAASoB,IAAI;IAElC,uCAAuC;IACvC,MAAMd,QAAQa,OAAOE,UAAU,EAAE,CAAC,EAAE,EAAEC,SAAShB,SAAS,EAAE;IAC1D,MAAMiB,WAAWjB,MAAMkB,IAAI,CAAC,CAACC,IAAyBA,EAAElB,IAAI,KAAKmB;IACjE,MAAMC,gBAAgBJ,UAAUhB;IAEhC,IAAI,CAACoB,eAAe;QAClB,MAAM,IAAIT,MAAM,CAAC,+CAA+C,EAAEf,KAAKC,SAAS,CAACe,SAAS;IAC5F;IAEA,OAAOS,yBAAyBD,eAAe/B,MAAMC,MAAM;AAC7D;AAEA,SAASE,uBACPH,KAAe,EACfF,YAAoB,EACpBC,YAAoB;IAEpB,MAAMkC,YAAY1B,KAAKC,SAAS,CAACR;IAEjC,OAAO,CAAC,sEAAsE,EAAEF,aAAa,IAAI,EAAEC,aAAa;;;;;;;;;;AAUlH,EAAEkC,UAAU;;;0CAG8B,CAAC;AAC3C;AAEA,SAASD,yBAAyB5B,QAAgB,EAAE8B,aAAqB;IACvE,IAAIC,UAAU/B,SAASgC,IAAI;IAE3B,gCAAgC;IAChC,IAAID,QAAQE,UAAU,CAAC,QAAQ;QAC7BF,UAAUA,QAAQG,OAAO,CAAC,gBAAgB,IAAIA,OAAO,CAAC,QAAQ,IAAIF,IAAI;IACxE;IAEA,IAAI;QACF,MAAMG,eAAehC,KAAKiC,KAAK,CAACL;QAEhC,IAAI,CAACM,MAAMC,OAAO,CAACH,eAAe;YAChC,MAAM,IAAIjB,MAAM;QAClB;QAEA,IAAIiB,aAAatC,MAAM,KAAKiC,eAAe;YACzCS,QAAQC,IAAI,CAAC,CAAC,SAAS,EAAEV,cAAc,mBAAmB,EAAEK,aAAatC,MAAM,EAAE;QACnF;QAEA,OAAOsC;IACT,EAAE,OAAOM,QAAQ;QACfF,QAAQtB,KAAK,CAAC,yCAAyCjB;QACvD,MAAM,IAAIkB,MAAM;IAClB;AACF"}
1
+ {"version":3,"sources":["../../src/services/gemini.ts"],"sourcesContent":["import type { GeminiTranslationRequest } from '../types.js'\n\ntype TranslateWithGeminiArgs = {\n apiKey: string\n} & GeminiTranslationRequest\n\nconst GEMINI_API_URL =\n 'https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent'\n\nexport async function translateWithGemini({\n apiKey,\n sourceLocale,\n targetLocale,\n texts,\n}: TranslateWithGeminiArgs): Promise<string[]> {\n if (texts.length === 0) {\n return []\n }\n\n const prompt = buildTranslationPrompt(texts, sourceLocale, targetLocale)\n\n const response = await fetch(`${GEMINI_API_URL}?key=${apiKey}`, {\n body: JSON.stringify({\n contents: [\n {\n parts: [{ text: prompt }],\n },\n ],\n generationConfig: {\n maxOutputTokens: 8192,\n temperature: 0.1,\n thinkingConfig: {\n thinkingBudget: 0,\n },\n topP: 0.95,\n },\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n const error = await response.text()\n throw new Error(`Gemini API error: ${error}`)\n }\n\n const result = await response.json()\n\n // Handle response - find the text part\n const parts = result.candidates?.[0]?.content?.parts || []\n const textPart = parts.find((p: { text?: string }) => p.text !== undefined)\n const generatedText = textPart?.text\n\n if (!generatedText) {\n throw new Error(`No translation returned from Gemini. Response: ${JSON.stringify(result)}`)\n }\n\n return parseTranslationResponse(generatedText, texts.length)\n}\n\nfunction buildTranslationPrompt(\n texts: string[],\n sourceLocale: string,\n targetLocale: string,\n): string {\n const textsJson = JSON.stringify(texts)\n\n return `You are a professional translator. Translate the following texts from ${sourceLocale} to ${targetLocale}.\n\nIMPORTANT RULES:\n1. Maintain the exact same formatting, including HTML tags, markdown, and special characters\n2. Do not translate proper nouns, brand names, or code/technical terms unless they have standard translations\n3. Preserve any placeholder variables like {{name}} or {0}\n4. Return ONLY a valid JSON array with the translations in the same order\n5. Each translation should correspond to the input at the same index\n\nInput texts (JSON array):\n${textsJson}\n\nReturn ONLY the JSON array of translations, nothing else. Example format:\n[\"translated text 1\", \"translated text 2\"]`\n}\n\nfunction parseTranslationResponse(response: string, expectedCount: number): string[] {\n let jsonStr = response.trim()\n\n // Handle code blocks if present\n if (jsonStr.startsWith('```')) {\n jsonStr = jsonStr.replace(/```json?\\n?/g, '').replace(/```/g, '').trim()\n }\n\n try {\n const translations = JSON.parse(jsonStr)\n\n if (!Array.isArray(translations)) {\n throw new Error('Response is not an array')\n }\n\n if (translations.length !== expectedCount) {\n console.warn(`Expected ${expectedCount} translations, got ${translations.length}`)\n }\n\n return translations\n } catch (_error) {\n console.error('Failed to parse translation response:', response)\n throw new Error('Failed to parse translation response from Gemini')\n }\n}\n"],"names":["GEMINI_API_URL","translateWithGemini","apiKey","sourceLocale","targetLocale","texts","length","prompt","buildTranslationPrompt","response","fetch","body","JSON","stringify","contents","parts","text","generationConfig","maxOutputTokens","temperature","thinkingConfig","thinkingBudget","topP","headers","method","ok","error","Error","result","json","candidates","content","textPart","find","p","undefined","generatedText","parseTranslationResponse","textsJson","expectedCount","jsonStr","trim","startsWith","replace","translations","parse","Array","isArray","console","warn","_error"],"mappings":"AAMA,MAAMA,iBACJ;AAEF,OAAO,eAAeC,oBAAoB,EACxCC,MAAM,EACNC,YAAY,EACZC,YAAY,EACZC,KAAK,EACmB;IACxB,IAAIA,MAAMC,MAAM,KAAK,GAAG;QACtB,OAAO,EAAE;IACX;IAEA,MAAMC,SAASC,uBAAuBH,OAAOF,cAAcC;IAE3D,MAAMK,WAAW,MAAMC,MAAM,GAAGV,eAAe,KAAK,EAAEE,QAAQ,EAAE;QAC9DS,MAAMC,KAAKC,SAAS,CAAC;YACnBC,UAAU;gBACR;oBACEC,OAAO;wBAAC;4BAAEC,MAAMT;wBAAO;qBAAE;gBAC3B;aACD;YACDU,kBAAkB;gBAChBC,iBAAiB;gBACjBC,aAAa;gBACbC,gBAAgB;oBACdC,gBAAgB;gBAClB;gBACAC,MAAM;YACR;QACF;QACAC,SAAS;YACP,gBAAgB;QAClB;QACAC,QAAQ;IACV;IAEA,IAAI,CAACf,SAASgB,EAAE,EAAE;QAChB,MAAMC,QAAQ,MAAMjB,SAASO,IAAI;QACjC,MAAM,IAAIW,MAAM,CAAC,kBAAkB,EAAED,OAAO;IAC9C;IAEA,MAAME,SAAS,MAAMnB,SAASoB,IAAI;IAElC,uCAAuC;IACvC,MAAMd,QAAQa,OAAOE,UAAU,EAAE,CAAC,EAAE,EAAEC,SAAShB,SAAS,EAAE;IAC1D,MAAMiB,WAAWjB,MAAMkB,IAAI,CAAC,CAACC,IAAyBA,EAAElB,IAAI,KAAKmB;IACjE,MAAMC,gBAAgBJ,UAAUhB;IAEhC,IAAI,CAACoB,eAAe;QAClB,MAAM,IAAIT,MAAM,CAAC,+CAA+C,EAAEf,KAAKC,SAAS,CAACe,SAAS;IAC5F;IAEA,OAAOS,yBAAyBD,eAAe/B,MAAMC,MAAM;AAC7D;AAEA,SAASE,uBACPH,KAAe,EACfF,YAAoB,EACpBC,YAAoB;IAEpB,MAAMkC,YAAY1B,KAAKC,SAAS,CAACR;IAEjC,OAAO,CAAC,sEAAsE,EAAEF,aAAa,IAAI,EAAEC,aAAa;;;;;;;;;;AAUlH,EAAEkC,UAAU;;;0CAG8B,CAAC;AAC3C;AAEA,SAASD,yBAAyB5B,QAAgB,EAAE8B,aAAqB;IACvE,IAAIC,UAAU/B,SAASgC,IAAI;IAE3B,gCAAgC;IAChC,IAAID,QAAQE,UAAU,CAAC,QAAQ;QAC7BF,UAAUA,QAAQG,OAAO,CAAC,gBAAgB,IAAIA,OAAO,CAAC,QAAQ,IAAIF,IAAI;IACxE;IAEA,IAAI;QACF,MAAMG,eAAehC,KAAKiC,KAAK,CAACL;QAEhC,IAAI,CAACM,MAAMC,OAAO,CAACH,eAAe;YAChC,MAAM,IAAIjB,MAAM;QAClB;QAEA,IAAIiB,aAAatC,MAAM,KAAKiC,eAAe;YACzCS,QAAQC,IAAI,CAAC,CAAC,SAAS,EAAEV,cAAc,mBAAmB,EAAEK,aAAatC,MAAM,EAAE;QACnF;QAEA,OAAOsC;IACT,EAAE,OAAOM,QAAQ;QACfF,QAAQtB,KAAK,CAAC,yCAAyCjB;QACvD,MAAM,IAAIkB,MAAM;IAClB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-translate",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "AI-powered translation plugin for Payload",
5
5
  "license": "MIT",
6
6
  "author": "Bridger Tower",