Skip to content

Latest commit

 

History

History

translator

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Translator plugin for Payload 3.0 (beta)

Install

pnpm add @payload-enchants/translator

Video

screen-capture.2.mp4

Features:

  1. A flexible structure with resolvers that allows you to apply any kind of transformation to your localizated data.
  2. 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
  3. Out of the box supports 3 resolvers - Copy, Google Translate, OpenAI and your own can be written easily.
  4. Works with any nested document structure and 2 Rich Text editor adapters - Lexical and Slate.
  5. You can omit some fields to translate. Documentation

Usage:

import { buildConfig } from 'payload/config';
import { translator } from '@payload-enchants/translator';
import { copyResolver } from '@payload-enchants/translator/resolvers/copy';
import { googleResolver } from '@payload-enchants/translator/resolvers/google';
import { openAIResolver } from '@payload-enchants/translator/resolvers/openAI';

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!,
        }),
      ],
    }),
  ],
});

Resolvers

OpenAI

Config:

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
};

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`;
};

Google

Config:

export type GoogleResolverConfig = {
  apiKey: string; // API key
  chunkLength?: number; / /How many texts to include into 1 request, default: 100
};

Writing your own

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.',
        },
      },
    },
  },
});

Using the Local API

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" }
});

Example of the hook that uses local operation to copy the doc data to other locales

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,
    });
  }
};

Omitting fields

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',
};