pnpm add @payload-enchants/translator
screen-capture.2.mp4
- A flexible structure with resolvers that allows you to apply any kind of transformation to your localizated data.
- Can be used not only from the admin panel, but within Local API as well. Example of the hook that automatically fills the other locales data on create
- Out of the box supports 3 resolvers - Copy, Google Translate, OpenAI and your own can be written easily.
- Works with any nested document structure and 2 Rich Text editor adapters - Lexical and Slate.
- You can omit some fields to translate. Documentation
import { buildConfig } from 'payload/config';
import {
translator,
copyResolver,
googleResolver,
openAIResolver,
libreResolver,
} from '@payload-enchants/translator';
export default buildConfig({
plugins: [
translator({
// collections with the enabled translator in the admin UI
collections: ['posts', 'small-posts'],
// globals with the enabled translator in the admin UI
globals: [],
// add resolvers that you want to include, examples on how to write your own in ./plugin/src/resolvers
resolvers: [
copyResolver(),
googleResolver({
apiKey: process.env.GOOGLE_API_KEY!,
}),
openAIResolver({
apiKey: process.env.OPENAI_KEY!,
}),
libreResolver({
apiKey: process.env.LIBRE_KEY!,
}),
],
}),
],
});
export type OpenAIResolverConfig = {
apiKey: string; // API key
chunkLength?: number; // How many texts to include into 1 request, default: 100
model?: string; // model, default: 'gpt-3.5-turbo'
promt?: OpenAIPrompt; // custom prompt
};
export type OpenAIPrompt = (args: {
localeFrom: string;
localeTo: string;
texts: string[];
}) => string;
// Default
const defaultPromt: OpenAIPrompt = ({ localeFrom, localeTo, texts }) => {
return `Translate me the following array: ${JSON.stringify(texts)} in locale=${localeFrom} to locale ${localeTo}, respond me with the same array structure`;
};
export type GoogleResolverConfig = {
apiKey: string; // API key
chunkLength?: number; / /How many texts to include into 1 request, default: 100
};
import type { TranslateResolver } from '@payload-enchants/translator/resolvers/types';
const myResolver: TranslateResolver = {
key: 'my',
resolve: async (args) => {
const { localeFrom, localeTo, req, texts } = args;
// here you can apply any kind of transformation to incoming texts, could be as well API call to a service.
const transformed = texts.map((each) => `${each} translated to ${localeTo}`);
return {
success: true,
translatedTexts: transformed,
};
},
};
export default buildConfig({
plugins: [
payloadPluginTranslator({
collections: ['posts', 'small-posts'],
globals: [],
resolvers: [myResolver],
}),
],
// apply translations that will be used for your resolver in the admin UI
i18n: {
supportedLanguages: { en },
translations: {
en: {
'plugin-translator': {
resolver_my_buttonLabel: 'Google Translate',
resolver_my_errorMessage: 'An error occurred when trying to translate the data',
resolver_my_modalTitle: 'Choose the locale to translate from',
resolver_my_submitButtonLabelEmpty: 'Translate only empty fields',
resolver_my_submitButtonLabelFull: 'Translate all',
resolver_my_successMessage: 'Successfully translated. Press "Save" to apply the changes.',
},
},
},
},
});
import { translateOperation } from '@payload-enchants/translator';
const translateResult = await translateOperation({
collectionSlug: 'posts', // or globalSlug if globals,
emptyOnly: false, // optional, should translate all the fields values or only fields that are empty, by default false.
id: postDefaultLocale.id, // pass the doc id if it's a collection
locale: 'de', // locale to translate to
localeFrom: 'en', // locale to translate from
payload, // payload instance, you can get it with getPayload or req.payload in hooks
req, // PayloadRequest, can be used instead of payload, more appreciable than payload if you have it
overrideAccess: false, //
resolver: 'copy', // pass resolver key
update: true, // optional, should update immediately or just return the translated result, default false
data: {}, // Optional, if you want to translate from your data passed here instead of the current doc, should be in "localeFrom" locale, for example { title: "Hello" }
});
import type { CollectionAfterChangeHook } from 'payload/types';
import { translateOperation } from '@payload-enchants/translator';
export const copyOtherLocales: CollectionAfterChangeHook = async ({
collection,
doc,
operation,
req,
}) => {
if (operation !== 'create') return;
const locale = req.locale;
if (!locale || !req.payload.config.localization) return;
const otherLocales = req.payload.config.localization.locales.filter(
(each) => each.code !== locale,
);
const { id } = doc;
for (const { code } of otherLocales) {
await translateOperation({
collectionSlug: collection.slug,
data: doc,
id,
locale: code,
localeFrom: locale,
req,
resolver: 'copy',
update: true,
});
}
};
To omit a specific field for translation, simply add custom.translatorSkip = true
to the field's config.
const field = {
custom: {
translatorSkip: true,
},
localized: true,
name: 'skip',
type: 'text',
};