payload-translate 1.0.2 → 1.0.3

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.
@@ -25,13 +25,17 @@ export const TranslateButton = ()=>{
25
25
  }
26
26
  setIsTranslating(true);
27
27
  closeModal(MODAL_SLUG);
28
+ // Determine target locales based on selection
29
+ const targetLocales = selectedLocale.value === '__all__' ? availableTargetLocales.map((l)=>typeof l === 'string' ? l : l.code) : [
30
+ selectedLocale.value
31
+ ];
28
32
  try {
29
33
  const response = await fetch(`${config.serverURL}${config.routes.api}/translate`, {
30
34
  body: JSON.stringify({
31
35
  collection: collectionSlug,
32
36
  documentId: id,
33
37
  sourceLocale: locale.code,
34
- targetLocale: selectedLocale.value
38
+ targetLocales
35
39
  }),
36
40
  credentials: 'include',
37
41
  headers: {
@@ -53,6 +57,7 @@ export const TranslateButton = ()=>{
53
57
  setSelectedLocale(null);
54
58
  }
55
59
  }, [
60
+ availableTargetLocales,
56
61
  closeModal,
57
62
  collectionSlug,
58
63
  config,
@@ -70,18 +75,24 @@ export const TranslateButton = ()=>{
70
75
  if (!config.localization || availableTargetLocales.length === 0 || !id) {
71
76
  return null;
72
77
  }
73
- const localeOptions = availableTargetLocales.map((l)=>{
74
- if (typeof l === 'string') {
78
+ const localeOptions = [
79
+ {
80
+ label: 'Translate to All',
81
+ value: '__all__'
82
+ },
83
+ ...availableTargetLocales.map((l)=>{
84
+ if (typeof l === 'string') {
85
+ return {
86
+ label: l.toUpperCase(),
87
+ value: l
88
+ };
89
+ }
75
90
  return {
76
- label: l.toUpperCase(),
77
- value: l
91
+ label: typeof l.label === 'string' ? l.label : l.code.toUpperCase(),
92
+ value: l.code
78
93
  };
79
- }
80
- return {
81
- label: typeof l.label === 'string' ? l.label : l.code.toUpperCase(),
82
- value: l.code
83
- };
84
- });
94
+ })
95
+ ];
85
96
  return /*#__PURE__*/ _jsxs(_Fragment, {
86
97
  children: [
87
98
  /*#__PURE__*/ _jsx(Button, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/TranslateButton/index.tsx"],"sourcesContent":["'use client'\n\nimport type { Locale } from 'payload'\n\nimport {\n Button,\n Modal,\n ReactSelect,\n type ReactSelectOption,\n useConfig,\n useDocumentInfo,\n useLocale,\n useModal,\n} from '@payloadcms/ui'\nimport React, { useCallback, useState } from 'react'\nimport { toast } from 'sonner'\n\nimport './index.scss'\n\nconst MODAL_SLUG = 'translate-document-modal'\n\nexport const TranslateButton: React.FC = () => {\n const { config } = useConfig()\n const { id, collectionSlug } = useDocumentInfo()\n const locale = useLocale()\n const { closeModal, openModal } = useModal()\n const [isTranslating, setIsTranslating] = useState(false)\n const [selectedLocale, setSelectedLocale] = useState<null | ReactSelectOption>(null)\n\n // Get available locales (exclude current locale)\n const localization = config.localization\n const locales = localization ? localization.locales : []\n const availableTargetLocales = locales.filter((l: Locale | string) => {\n const code = typeof l === 'string' ? l : l.code\n return code !== locale?.code\n })\n\n const handleTranslate = useCallback(async () => {\n if (!collectionSlug || !id || !locale?.code || !selectedLocale) {\n return\n }\n\n setIsTranslating(true)\n closeModal(MODAL_SLUG)\n\n try {\n const response = await fetch(`${config.serverURL}${config.routes.api}/translate`, {\n body: JSON.stringify({\n collection: collectionSlug,\n documentId: id,\n sourceLocale: locale.code,\n targetLocale: selectedLocale.value,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n const result = await response.json()\n\n if (result.success) {\n toast.success(result.message || 'Translation complete')\n } else {\n toast.error(result.error || 'Translation failed')\n }\n } catch (error) {\n toast.error('Translation request failed')\n console.error('Translation error:', error)\n } finally {\n setIsTranslating(false)\n setSelectedLocale(null)\n }\n }, [closeModal, collectionSlug, config, id, locale, selectedLocale])\n\n const handleCancel = useCallback(() => {\n setSelectedLocale(null)\n closeModal(MODAL_SLUG)\n }, [closeModal])\n\n // Don't render if no localization, no other locales, or document not saved yet\n if (!config.localization || availableTargetLocales.length === 0 || !id) {\n return null\n }\n\n const localeOptions: ReactSelectOption[] = availableTargetLocales.map((l: Locale | string) => {\n if (typeof l === 'string') {\n return { label: l.toUpperCase(), value: l }\n }\n return {\n label: typeof l.label === 'string' ? l.label : l.code.toUpperCase(),\n value: l.code,\n }\n })\n\n return (\n <>\n <Button\n buttonStyle=\"secondary\"\n disabled={isTranslating}\n onClick={() => openModal(MODAL_SLUG)}\n >\n {isTranslating ? 'Translating...' : 'Translate'}\n </Button>\n <Modal className=\"translate-modal\" slug={MODAL_SLUG}>\n <div className=\"translate-modal__wrapper\">\n <div className=\"translate-modal__content\">\n <h3>Translate Document</h3>\n <p>\n Translate content from <strong>{locale?.code?.toUpperCase()}</strong> to:\n </p>\n <div className=\"translate-modal__select\">\n <ReactSelect\n isClearable\n onChange={(option) => setSelectedLocale(option as ReactSelectOption)}\n options={localeOptions}\n placeholder=\"Select target locale...\"\n value={selectedLocale ?? undefined}\n />\n </div>\n </div>\n <div className=\"translate-modal__controls\">\n <Button buttonStyle=\"secondary\" onClick={handleCancel} size=\"medium\">\n Cancel\n </Button>\n <Button disabled={!selectedLocale} onClick={handleTranslate} size=\"medium\">\n Translate\n </Button>\n </div>\n </div>\n </Modal>\n </>\n )\n}\n"],"names":["Button","Modal","ReactSelect","useConfig","useDocumentInfo","useLocale","useModal","React","useCallback","useState","toast","MODAL_SLUG","TranslateButton","config","id","collectionSlug","locale","closeModal","openModal","isTranslating","setIsTranslating","selectedLocale","setSelectedLocale","localization","locales","availableTargetLocales","filter","l","code","handleTranslate","response","fetch","serverURL","routes","api","body","JSON","stringify","collection","documentId","sourceLocale","targetLocale","value","credentials","headers","method","result","json","success","message","error","console","handleCancel","length","localeOptions","map","label","toUpperCase","buttonStyle","disabled","onClick","className","slug","div","h3","p","strong","isClearable","onChange","option","options","placeholder","undefined","size"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/TranslateButton/index.tsx"],"sourcesContent":["'use client'\n\nimport type { Locale } from 'payload'\n\nimport {\n Button,\n Modal,\n ReactSelect,\n type ReactSelectOption,\n useConfig,\n useDocumentInfo,\n useLocale,\n useModal,\n} from '@payloadcms/ui'\nimport React, { useCallback, useState } from 'react'\nimport { toast } from 'sonner'\n\nimport './index.scss'\n\nconst MODAL_SLUG = 'translate-document-modal'\n\nexport const TranslateButton: React.FC = () => {\n const { config } = useConfig()\n const { id, collectionSlug } = useDocumentInfo()\n const locale = useLocale()\n const { closeModal, openModal } = useModal()\n const [isTranslating, setIsTranslating] = useState(false)\n const [selectedLocale, setSelectedLocale] = useState<null | ReactSelectOption>(null)\n\n // Get available locales (exclude current locale)\n const localization = config.localization\n const locales = localization ? localization.locales : []\n const availableTargetLocales = locales.filter((l: Locale | string) => {\n const code = typeof l === 'string' ? l : l.code\n return code !== locale?.code\n })\n\n const handleTranslate = useCallback(async () => {\n if (!collectionSlug || !id || !locale?.code || !selectedLocale) {\n return\n }\n\n setIsTranslating(true)\n closeModal(MODAL_SLUG)\n\n // Determine target locales based on selection\n const targetLocales =\n selectedLocale.value === '__all__'\n ? availableTargetLocales.map((l: Locale | string) =>\n typeof l === 'string' ? l : l.code,\n )\n : [selectedLocale.value]\n\n try {\n const response = await fetch(`${config.serverURL}${config.routes.api}/translate`, {\n body: JSON.stringify({\n collection: collectionSlug,\n documentId: id,\n sourceLocale: locale.code,\n targetLocales,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n const result = await response.json()\n\n if (result.success) {\n toast.success(result.message || 'Translation complete')\n } else {\n toast.error(result.error || 'Translation failed')\n }\n } catch (error) {\n toast.error('Translation request failed')\n console.error('Translation error:', error)\n } finally {\n setIsTranslating(false)\n setSelectedLocale(null)\n }\n }, [availableTargetLocales, closeModal, collectionSlug, config, id, locale, selectedLocale])\n\n const handleCancel = useCallback(() => {\n setSelectedLocale(null)\n closeModal(MODAL_SLUG)\n }, [closeModal])\n\n // Don't render if no localization, no other locales, or document not saved yet\n if (!config.localization || availableTargetLocales.length === 0 || !id) {\n return null\n }\n\n const localeOptions: ReactSelectOption[] = [\n { label: 'Translate to All', value: '__all__' },\n ...availableTargetLocales.map((l: Locale | string) => {\n if (typeof l === 'string') {\n return { label: l.toUpperCase(), value: l }\n }\n return {\n label: typeof l.label === 'string' ? l.label : l.code.toUpperCase(),\n value: l.code,\n }\n }),\n ]\n\n return (\n <>\n <Button\n buttonStyle=\"secondary\"\n disabled={isTranslating}\n onClick={() => openModal(MODAL_SLUG)}\n >\n {isTranslating ? 'Translating...' : 'Translate'}\n </Button>\n <Modal className=\"translate-modal\" slug={MODAL_SLUG}>\n <div className=\"translate-modal__wrapper\">\n <div className=\"translate-modal__content\">\n <h3>Translate Document</h3>\n <p>\n Translate content from <strong>{locale?.code?.toUpperCase()}</strong> to:\n </p>\n <div className=\"translate-modal__select\">\n <ReactSelect\n isClearable\n onChange={(option) => setSelectedLocale(option as ReactSelectOption)}\n options={localeOptions}\n placeholder=\"Select target locale...\"\n value={selectedLocale ?? undefined}\n />\n </div>\n </div>\n <div className=\"translate-modal__controls\">\n <Button buttonStyle=\"secondary\" onClick={handleCancel} size=\"medium\">\n Cancel\n </Button>\n <Button disabled={!selectedLocale} onClick={handleTranslate} size=\"medium\">\n Translate\n </Button>\n </div>\n </div>\n </Modal>\n </>\n )\n}\n"],"names":["Button","Modal","ReactSelect","useConfig","useDocumentInfo","useLocale","useModal","React","useCallback","useState","toast","MODAL_SLUG","TranslateButton","config","id","collectionSlug","locale","closeModal","openModal","isTranslating","setIsTranslating","selectedLocale","setSelectedLocale","localization","locales","availableTargetLocales","filter","l","code","handleTranslate","targetLocales","value","map","response","fetch","serverURL","routes","api","body","JSON","stringify","collection","documentId","sourceLocale","credentials","headers","method","result","json","success","message","error","console","handleCancel","length","localeOptions","label","toUpperCase","buttonStyle","disabled","onClick","className","slug","div","h3","p","strong","isClearable","onChange","option","options","placeholder","undefined","size"],"mappings":"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"}
@@ -23,9 +23,9 @@ export const translateHandler = async (req)=>{
23
23
  status: 400
24
24
  });
25
25
  }
26
- const { collection, documentId, sourceLocale, targetLocale } = body;
26
+ const { collection, documentId, sourceLocale, targetLocales } = body;
27
27
  // Validate request
28
- if (!collection || !documentId || !sourceLocale || !targetLocale) {
28
+ if (!collection || !documentId || !sourceLocale || !targetLocales || targetLocales.length === 0) {
29
29
  return Response.json({
30
30
  error: 'Missing required fields',
31
31
  success: false
@@ -74,33 +74,38 @@ export const translateHandler = async (req)=>{
74
74
  return Response.json({
75
75
  message: 'No translatable fields found',
76
76
  success: true,
77
- translatedFields: 0
77
+ translatedFields: 0,
78
+ translatedLocales: 0
78
79
  });
79
80
  }
80
81
  // Prepare texts for translation
81
82
  const texts = translatableFields.map((f)=>f.value);
82
- // Translate with Gemini
83
- const translations = await translateWithGemini({
84
- apiKey,
85
- sourceLocale,
86
- targetLocale,
87
- texts
88
- });
89
- // Apply translations to document
90
- const updatedData = applyTranslations(document, translatableFields, translations);
91
- // Remove immutable system fields before update
92
- const { id, createdAt, updatedAt, ...dataToUpdate } = updatedData;
93
- // Update document in target locale
94
- await payload.update({
95
- id: documentId,
96
- collection,
97
- data: dataToUpdate,
98
- locale: targetLocale
99
- });
83
+ // Translate to each target locale
84
+ for (const targetLocale of targetLocales){
85
+ // Translate with Gemini
86
+ const translations = await translateWithGemini({
87
+ apiKey,
88
+ sourceLocale,
89
+ targetLocale,
90
+ texts
91
+ });
92
+ // Apply translations to document
93
+ const updatedData = applyTranslations(document, translatableFields, translations);
94
+ // Remove immutable system fields before update
95
+ const { id, createdAt, updatedAt, ...dataToUpdate } = updatedData;
96
+ // Update document in target locale
97
+ await payload.update({
98
+ id: documentId,
99
+ collection,
100
+ data: dataToUpdate,
101
+ locale: targetLocale
102
+ });
103
+ }
100
104
  return Response.json({
101
- message: `Successfully translated ${translatableFields.length} field(s)`,
105
+ message: `Successfully translated ${translatableFields.length} field(s) to ${targetLocales.length} locale(s)`,
102
106
  success: true,
103
- translatedFields: translatableFields.length
107
+ translatedFields: translatableFields.length,
108
+ translatedLocales: targetLocales.length
104
109
  });
105
110
  } catch (error) {
106
111
  console.error('Translation error:', error);
@@ -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, targetLocale } = body\n\n // Validate request\n if (!collection || !documentId || !sourceLocale || !targetLocale) {\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 } as TranslateResponse)\n }\n\n // Prepare texts for translation\n const texts = translatableFields.map((f) => f.value)\n\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 return Response.json({\n message: `Successfully translated ${translatableFields.length} field(s)`,\n success: true,\n translatedFields: translatableFields.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","targetLocale","apiKey","config","custom","translateApiKey","document","findByID","id","depth","locale","collectionConfig","collections","translatableFields","fields","length","message","translatedFields","texts","map","f","value","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 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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"}
package/dist/types.d.ts CHANGED
@@ -17,13 +17,14 @@ export type TranslateRequestBody = {
17
17
  collection: string;
18
18
  documentId: number | string;
19
19
  sourceLocale: string;
20
- targetLocale: string;
20
+ targetLocales: string[];
21
21
  };
22
22
  export type TranslateResponse = {
23
23
  error?: string;
24
24
  message?: string;
25
25
  success: boolean;
26
26
  translatedFields?: number;
27
+ translatedLocales?: number;
27
28
  };
28
29
  export type TranslatableField = {
29
30
  /**
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug } from 'payload'\n\nexport type PayloadTranslateConfig = {\n /**\n * Google Gemini API key for translations\n */\n apiKey: string\n /**\n * List of collection slugs to enable translation for\n */\n collections: CollectionSlug[]\n /**\n * Whether the plugin is disabled\n */\n disabled?: boolean\n}\n\nexport type TranslateRequestBody = {\n collection: string\n documentId: number | string\n sourceLocale: string\n targetLocale: string\n}\n\nexport type TranslateResponse = {\n error?: string\n message?: string\n success: boolean\n translatedFields?: number\n}\n\nexport type TranslatableField = {\n /**\n * For richText fields, the path within the Lexical structure to the text node\n * e.g., \"root.children.0.children.1\"\n */\n lexicalPath?: string\n path: string\n type: 'richText' | 'text' | 'textarea'\n value: string\n}\n\nexport type GeminiTranslationRequest = {\n sourceLocale: string\n targetLocale: string\n texts: string[]\n}\n"],"names":[],"mappings":"AA0CA,WAIC"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug } from 'payload'\n\nexport type PayloadTranslateConfig = {\n /**\n * Google Gemini API key for translations\n */\n apiKey: string\n /**\n * List of collection slugs to enable translation for\n */\n collections: CollectionSlug[]\n /**\n * Whether the plugin is disabled\n */\n disabled?: boolean\n}\n\nexport type TranslateRequestBody = {\n collection: string\n documentId: number | string\n sourceLocale: string\n targetLocales: string[]\n}\n\nexport type TranslateResponse = {\n error?: string\n message?: string\n success: boolean\n translatedFields?: number\n translatedLocales?: number\n}\n\nexport type TranslatableField = {\n /**\n * For richText fields, the path within the Lexical structure to the text node\n * e.g., \"root.children.0.children.1\"\n */\n lexicalPath?: string\n path: string\n type: 'richText' | 'text' | 'textarea'\n value: string\n}\n\nexport type GeminiTranslationRequest = {\n sourceLocale: string\n targetLocale: string\n texts: string[]\n}\n"],"names":[],"mappings":"AA2CA,WAIC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-translate",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "AI-powered translation plugin for Payload",
5
5
  "license": "MIT",
6
6
  "author": "Bridger Tower",