Extensions & Integrations

This bundle allows automatic translation of documents (pages and pieces) when localizing content. It comes with two translation providers: Google Cloud Translation and DeepL.
Upgrade your project
This is a premium module and requires a Pro license. More here.

ApostropheCMS logo

Automatic Translation

Scale your content globally with AI-powered translation for ApostropheCMS. Transform your localization workflow with automatic translation that handles pages, pieces, and complex content structures. Supporting DeepL, Google Cloud Translation, Azure Translator, and custom translation providers with intelligent retry mechanisms and customizable field mapping.

Why Automatic Translation?

  • 🌍 Global Content, Zero Friction: Transform your content into multiple languages instantly during the localization process
  • 🎯 Production-Ready Quality: Support for professional translation providers including DeepL, Google Cloud, Azure Translator, and custom providers
  • ⚡ Smart Field Handling: Automatically translates text fields, rich content, and widgets while preserving formatting
  • 🔧 Developer Friendly: Extensive customization options for field mapping, widget handling, and custom field types
  • 📋 Translation Workflow: Streamlined process for submitting content for translation and reviewing AI-generated results before publishing

Installation

Note: This module requires an ApostropheCMS Pro license. Don't have Pro yet? Create an account on Apostrophe Workspaces or contact us and get started with ApostropheCMS Pro to access this and other Pro extensions.

npm install @apostrophecms-pro/automatic-translation

Quick Start Guide

Choose your preferred translation provider and follow the setup instructions below. All providers offer professional-grade translation quality with different strengths and pricing models.

DeepL Setup

Get your API key from your DeepL Account. Copy the key from the "Authentication Key for DeepL API" section.

Environment variable setup (recommended):

export APOS_DEEPL_API_SECRET=your-key-here
npm start

Configuration:

import apostrophe from 'apostrophe';

apostrophe({
  root: import.meta,
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'deepl'
      }
    },
    '@apostrophecms-pro/automatic-translation-deepl': {}
  }
});

Alternative configuration with API key in code:

import apostrophe from 'apostrophe';

apostrophe({
  root: import.meta,
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'deepl'
      }
    },
    '@apostrophecms-pro/automatic-translation-deepl': {
      options: {
        apiSecret: 'your-key-here'
      }
    }
  }
});

Language Mapping for DeepL

Your project locales might not match exactly with DeepL's supported languages. Configure mappings as needed:

'@apostrophecms-pro/automatic-translation-deepl': {
  options: { 
    // Map project locales to DeepL source languages (no country codes supported)
    sourcesMapping: {
      'en-US': 'en',    
    },
    // Map project locales to DeepL target languages
    targetsMapping: {
      'en-GB': 'en-US', // DeepL doesn't support 'en' for targets
      'pt': 'pt-PT'     // Default mapping provided
    }
  }
}

Note: DeepL sources only support languages without country codes. The module removes country codes by default, so you might not need source mapping. Some default target mappings are provided for common locales like en and pt to avoid errors.

For supported languages, check the DeepL documentation.

Translation Provider Setup

Google Cloud Translation

Requires a Google Cloud project with Translation API enabled and a service account key. Complete setup guide.

Environment variables (recommended):

export APOS_GOOGLE_TRANSLATE_PROJECT_ID=your-project-id
export APOS_GOOGLE_TRANSLATE_KEY_FILENAME=/path/to/keyfile.json
npm start

Configuration:

import apostrophe from 'apostrophe';

apostrophe({
  root: import.meta,
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'google'
      }
    },
    '@apostrophecms-pro/automatic-translation-google': {}
  }
});

Alternative configuration with credentials in code:

import apostrophe from 'apostrophe';

apostrophe({
  root: import.meta,
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'google'
      }
    },
    '@apostrophecms-pro/automatic-translation-google': {
      options: {
        projectId: 'your-project-id',
        keyFilename: '/path/to/keyfile.json'
      }
    }
  }
});

Azure AI Translation

Get your API key from Azure AI Services. Follow the Azure documentation to create a Translator resource and obtain your authentication key.

Environment variable (recommended):

export APOS_AZURE_API_SECRET=your-key-here
npm start

Configuration:

import apostrophe from 'apostrophe';

apostrophe({
  root: import.meta,
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'azure'
      }
    },
    '@apostrophecms-pro/automatic-translation-azure': {}
  }
});

Alternative configuration with API key in code:

import apostrophe from 'apostrophe';

apostrophe({
  root: import.meta,
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'azure'
      }
    },
    '@apostrophecms-pro/automatic-translation-azure': {
      options: {
        apiSecret: 'your-key-here'
      }
    }
  }
});

Language Mapping for Azure

Your project locales might not match exactly with Azure's supported languages. Configure mappings as needed:

'@apostrophecms-pro/automatic-translation-azure': {
  options: {
    // Map project locales to Azure language codes
    languageMapping: {
      'en-US': 'en',
      'pt': 'pt-PT',     // Default provided by module
      'pt-BR': 'pt'      // Default provided by module
    }
  }
}

Important: Setting a custom languageMapping completely overrides the default mappings. If you need the defaults (like pt: 'pt-PT'), you must include them explicitly in your custom mapping.

Advanced Azure Configuration

Optional settings for specific Azure service configurations:

'@apostrophecms-pro/automatic-translation-azure': {
  options: {
    region: 'eastus',  // Required for multi-service or regional resources
    baseUrl: 'https://api.cognitive.microsofttranslator.com',  // Custom domain
    translateOptions: {
      // Additional query parameters for Azure Translation API
      // See Azure documentation for available options
      category: 'general',
      profanityAction: 'NoAction'
    }
  }
}

How It Works

When configured, the module adds a "Translate text content" option to the localization dialog. It will show a new Translate text content option under the Automatic translation settings section in the localization dialog. This intelligently translates:

  • All string and slug fields throughout your content structure
  • Rich text widgets with proper HTML preservation
  • Custom widgets and nested content structures
  • Area widgets while maintaining layout and formatting
  • The html widget is disabled by default. You can enable it in your project by setting the translate option to true in the widget module definition (see the "Configure widgets" section below).

Localization Wizard

Only languages supported by both your project and the selected translation provider will be available for translation. It will skip the translation when a language is not supported, and show a message about it.

Configuration Options

Module-Level Settings

Configure retry behavior and rate limiting:

'@apostrophecms-pro/automatic-translation': {
  options: {
    provider: 'deepl',
    retryBaseDelayMs: 1000,  // Default initial retry delay (1 second)
    retryMaxAttempts: 5      // Default maximum retry attempts
  }
}

Retry Mechanism: The module automatically handles rate limiting from translation providers using exponential backoff. When a provider responds with a 429 (Too Many Requests) error, the module will retry with increasing delays calculated as:

delay = lastDelay * 2 * (1 + random(0, 1))

Where lastDelay is the delay of the previous attempt with an initial value retryBaseDelayMs and random(0, 1) is a random number between 0 and 1. The retry attempts will be stopped when the retryMaxAttempts is reached. This ensures reliable translation even during high-traffic periods or when hitting API rate limits.

Field-Level Control

Exclude specific fields from translation:

// modules/my-module/index.js
export default {
  fields: {
    add: {
      internalCode: {
        type: 'string',
        label: 'Internal Reference Code',
        translate: false  // Skip translation
      },
      description: {
        type: 'string',
        label: 'Product Description'
        // Will be translated by default
      }
    }
  }
};

Widget-Level Control

Control translation for entire widget types:

// modules/my-widget/index.js
export default {
  extend: '@apostrophecms/widget-type',
  options: {
    label: 'My Custom Widget',
    translate: false  // Skip all fields in this widget
  }
};

Or disable specific widget instances in areas:

// modules/my-page/index.js
export default {
  fields: {
    add: {
      main: {
        type: 'area',
        options: {
          widgets: {
            'code-block': {
              options: {
                translate: false  // Don't translate code examples
              }
            },
            'rich-text': {}  // Will be translated
          }
        }
      }
    }
  }
};

User Interface Customization

Custom Labels and Disclaimers

There are two keys available for altering and localizing the text for the translation checkbox:

  • automaticTranslationCheckboxHelp used to display additional details or acknowledgement after the Translate text content checkbox label
  • automaticTranslationDisclaimer used to display a disclaimer to your users before the Translate text content checkbox

Create modules/@apostrophecms/i18n/i18n/en.json to customize user-facing text:

{
  "automaticTranslationCheckboxHelp": "By clicking the below checkbox, you acknowledge that translations are generated by AI and may require review.",
  "automaticTranslationDisclaimer": "Translations are produced by AI. They can be inaccurate, omit nuance, or introduce formatting issues. Review all translated content before you publish."
}

Change the "Translated with AI" indicator in modules/@apostrophecms-pro/automatic-translation/i18n/en.json:

{
  "fieldMeta": "AI Translated ✨"
}

Advanced Development

Custom field types

If you have custom field types that contain text, and you want to translate them, you can adapt the type slightly to support that. Here is a basic example of a custom field type that supports translation:

// in modules/my-module/index.js
module.exports = {
  init(self) {
    self.apos.schema.addFieldType({
      // This indicates that the field type supports translation
      translate: true,
      name: 'myCustomField',
      // Let's assume the field contains just text
      convert(req, field, data, object) {
        const input = data[field.name];
        object[field.name] = self.apos.launder.string(input);
      },
      vueComponent: 'MyCustomField'
    });
  }
};

In this example, we have a simple field type that contains plain text (not HTML). We set the translate option to true and the module will automatically translate the field when localizing the document.

A more advanced example would be a field type that contains e.g., an object with some text properties. In that case, you would need to implement the getText and convertText methods in the field type definition:

// in modules/my-module/index.js
module.exports = {
  init(self) {
    self.apos.schema.addFieldType({
      // This indicates that the field type supports translation
      translate: true,
      name: 'myCustomField',
      convert(req, field, data, object) {
        // ... your logic here
      },
      getText(field, value, valuePath, schemaPath) {
        // let's assume the field value contains two properties of interest -
        // `text1` and `text2`.
        // For every item of the returned array, `convertText` method will be called
        // with `translated` value and the item as `meta` object.
        return [
          // Export `text1` for translation
          {
            valuePath: [ ...valuePath, field.name, 'text1' ],
            schemaPath: [ ...schemaPath, field.name ],
            text: value.text1,
            type: field.type
          },
          // Export `text2` for translation
          {
            valuePath: [ ...valuePath, field.name, 'text2' ],
            schemaPath: [ ...schemaPath, field.name ],
            text: value.text2,
            type: field.type
          },
          // You can also export only meta information in order to
          // mark the field as "translated" in the UI. Add custom property
          // `original` that can be accessed in the `convertText` method.
          // See full explanation of metaOnly below.
          {
            metaOnly: true,
            valuePath: [ ...valuePath, field.name ],
            schemaPath: [ ...schemaPath, field.name ],
            original: value,
            type: field.type
          }
        ]
      },
      // `translated` is our entire object for the meta only field, and the
      // text for the other fields that we have exported in the `getText` method.
      // `meta` is the meta information that we have exported in the `getText` method.
      convertText(translated, meta) {
        // If the metaOnly result is passed, we can compute.
        // See full explanation of metaOnly below
        if (meta.metaOnly) {
          const { original, valuePath, type } = meta;
          // Compute the changed state of the field.
          // This data will be available in the UI indicator component
          return {
            valuePath,
            type,
            changed: original.text1 !== translated.text1 || original.text2 !== translated.text2
          };
        }

        // For our text data, we can use the `he` library to decode HTML entities
        // that were eventually encoded by the translation provider.
        // Or you can just return the translated text as is.
        const converted = he.decode(translated || '');
        return {
          ...meta,
          translated: converted,
          changed: meta.text.trim() !== converted.trim()
        };
      },
      vueComponent: 'MyCustomField'
    });
  }
};

Learn more about the translation and field metadata in the dedicated section below.

In your Vue component, you can pass the meta information so that the "Translated" indicator can be displayed:

<template>
  <!-- Add :meta="fieldMeta" prop here -->
  <AposInputWrapper
    :modifiers="modifiers" :field="field"
    :error="effectiveError" :uid="uid"
    :display-options="displayOptions"
    :meta="fieldMeta"
  >
  <!-- ... your input component here -->
  </AposInputWrapper>
</template>

<script>
import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin';
export default {
  name: 'AposInputString',
  // the mixin will handle the meta information
  mixins: [ AposInputMixin ],
  // ... your component logic here
}
</script>

Advanced widget support

Your widget can override the default text extraction if needed. By default, the widgets will use getText and convertText on their underlying field schema. If this is not working for your custom widget, you can override this behavior and return your desired list of properties to be translated. You can do this by implementing the getTranslationText and convertTranslationText methods in your widget module:

// in modules/my-widget/index.js
module.exports = {
  extend: '@apostrophecms/widget',
  options: {
    label: 'My Widget',
    translate: true
  },
  methods(self) {
    return {
      getTranslationText(value, schemaPath) {
        // ... your logic here
      },
      convertTranslationText(translated, meta) {
        // ... your logic here
      }
    };
  }
};

getTranslationText and convertTranslationText methods implement the same logic as getText and convertText methods in the custom field type example. The only difference is that getTranslationText receives only the widget value and the schema path. The method names are different to avoid conflicts with existing methods in the widget modules. Learn more about the translation meta and the methods logic in the next section.

Your custom translation methods should carry the logic of extracting and converting the text from the widget value. You can look at the code in modules/@apostrophecms-pro/automatic-translation-rich-text-widget for an advanced example of how to handle widget-specific text extraction and conversion.

Translation meta and field meta explained

First, let's clarify the terms.

Translation meta, in the context of this module, is the information extracted from a document in the form of a list of objects, each containing information about:

  • the path to a text value in the document
  • the type of field that owns the text
  • any additional information that might be useful for the translation provider, the current field type or the UI

Keep in mind that one schema field can have multiple text values, and each of them will be translated separately, and exported as a separate meta object.

Field meta is field-specific information that is added to the document. This is a core feature that allows the storage of additional information about a schema field. This feature is currently used to store the "changed" status of the field after translation, the "original" text and the document value path to the text.

How it works?

  1. The document translation meta is extracted from the document. It contains per-field meta (as explained above). Here is where the getText method per field is used.
  2. The translation provider translates the text and returns the translated text.
  3. The convertText method is called on every translated text. This allows the field type to perform any formatting on the translated text (e.g. escape HTML entities when required) but also to add additional meta information. For example, we add changed status by comparing the original and translated text.
  4. The translated text replaces the original text in the document.
  5. The translation meta per field is stored as a field meta in the document.
  6. The UI can use the field meta to display the "Translated" indicator.

Basic example

Let's look closer at the getText logic and its return format. A typical text field will be handled similarly to this:

getText(field, value, valuePath, schemaPath) {
  return [
    {
      valuePath: [ ...valuePath, field.name ],
      schemaPath: [ ...schemaPath, field.name ],
      text: value,
      type: field.type
    }
  ];
}

The field argument is the schema field definition, value is the value of the field in the document. valuePath is an array of parent field names that lead to the current field, something we call pathComponents. The method should always (with one exception explained below) return an array of objects, each containing the valuePath, the text and the type of the field. The schemaPath is an array of parent field names that lead to the current field in the schema. This is useful when you need to extract additional meta data from the schema fields for a given translation meta item. You can use the eachSchemaField(schemaPath, schema, callback) method to walk through the schema fields and call a callback on each of them (see below for more details). Adding more arbitrary properties to the object is allowed.

With the simple example above, the next step according to our logic is to call the convertText method for each of the returned objects when the translation is done. The convertText method will receive the translated text and the meta object. Here is an example of how the corresponding convertText method can look like:

convertText(translated, meta) {
  const converted = he.decode(translated || '');

  return {
    ...meta,
    translated: converted,
    changed: meta.text.trim() !== converted.trim()
  };
}

The same meta object that was returned from the getText method is passed to the convertText method. We add the translated property to the meta object after converting the translated text.

We use the he package to unescape any HTML entities that might have been encoded by the translation provider. We also add the changed status to the meta object. This status is used by the UI to display the "Translated" indicator. Any additional properties that are added to the meta object will be stored later as field meta in the document.

After the conversion, our meta object will look like this:

{
  valuePath: [ 'myField' ],
  text: 'Original text',
  type: 'string',
  translated: 'Translated text',
  changed: true
}

The internal bundle engine will first use this to replace the translated text in the document. It will set doc.myField to Translated text. Then, it will store some of the properties as field meta in the document, using code similar to the following:

self.apos.schema.setMeta(doc, '@apostrophecms/automatic-translation', 'myField', 'data', {
  valuePath: 'myField',
  text: 'Original text',
  type: 'string',
  changed: true
});

The payload (last argument) will be stored in a key, data for the path (path components) myField under namespace @apostrophecms/automatic-translation in the document.

Private meta properties

It's possible to pass useful data between getText and convertText while keeping those private, not saved as field meta in the document. Here is an example with a private property original:

getText(field, value, valuePath, schemaPath) {
  // Logic that would extract `/parent-path/my-slug` from the value
  // and transform it to `my slug`.
  const slugText = transformSlugToText(value);
  return [
    {
      valuePath: [ ...valuePath, field.name ],
      schemaPath: [ ...schemaPath, field.name ],
      text: slugText,
      original: value,
      type: field.type,
    }
  ];
},
convertText(translated, meta) {
  const { original, ...rest } = meta;
  // Logic that will transform `my translated slug` 
  // to `/parent-path/my-translated-slug`
  const slug = transformTextToSlug(translated, original);

  // Omit the `original` property from the meta object
  return {
    ...rest,
    translated: slug,
    changed: original.trim() !== slug.trim()
  };
}

The original property is introduced in the getText method and is passed to the convertText method after successful translation. The original property is omitted from the returned meta object in the convertText method. The original property is not saved as metadata in the document. It's a private property used internally to deliver data between the getText (before translation) and convertText (after translation) methods.

Unique value path

The previous example works well for simple, root-level document properties. What about widgets and array items? The problem with these is that their position in the document may change. To address this, we need to use a unique value path. Luckily, this is already handled well by the Apostrophe core. Every array or area item has a unique _id property. A unique path to any such item is possible by using @{_id} as a starting path component. Here is an example of a rich text widget value:

{
  _id: 'uniqueId',
  content: 'Original text',
  // ... other properties
}

Its path components would be:

[ '@uniqueId', 'content' ]

The string version of the path above is @uniqueId.content. The methods for the rich text widget would look like this:

getTranslationText(value, schemaPath) {
  return [
    {
      valuePath: [ `@${value._id}`, 'content' ],
      metaPath: [ `@${value._id}` ],
      schemaPath,
      text: value.content,
      type: field.type
    }
  ];
},
convertTranslationText(translated, meta) {
  // No need to transform the text, as it's already HTML
  return {
    ...meta,
    translated,
    changed: meta.text.trim() !== translated.trim()
  };
}

You might have noticed that we have introduced a new metaPath property. Most of the time the path components passed to the apos.schema.setMeta() are the same as our valuePath that represents the path components to the document value. However, in the case of more complex value structures, this won't be the case anymore. When available, metaPath will be used to store the field meta in the document:

self.apos.schema.setMeta(doc, '@apostrophecms/automatic-translation', '@uniqueId', 'data', {
  valuePath: '@uniqueId.content',
  text: 'Original text',
  type: 'string',
  changed: true
});

Here we have widgetValue.content property, containing the text we want to translate. The content property is an internal implementation detail and it should not be used when adding the field meta. We can use metaPath in order to provide the value as required by the apos.schema.setMeta() method.

Meta only fields and the metaOnly property

Let's look at another scenario - array fields. The array field does not directly participate in translation but returns the array of fields that should be translated for its schema. At the same time, we need a way to add field meta for the array field so that the UI can see that this array field has "changed" status. We can use a separate meta object and the metaOnly property for that. Here is an example of how the getText and convertText methods can look like for an array field (pseudo code):

getText(field, value, valuePath, schemaPath) {
  // Extract the translation meta for the array items
  const meta = getChildrenMeta(field.schema, value);

  // Add the meta for the array field itself
  if (meta.length) {
    meta.push({
      metaOnly: true,
      valuePath: [ ...valuePath, field.name ],
      schemaPath: [ ...schemaPath, field.name ],
      type: field.type
    });
  }

  return meta;
},
convertText(translated, meta) {
  // This should never happen.
  if (!meta.metaOnly) {
    throw new Error('This method should be called only for my meta only fields');
  }
  return {
    ...meta,
    changed: translated.length > 0
  };
}

The getText retrieves the translation meta for the array items. It also adds a meta object for the array field itself. It sets the metaOnly property to true in the array field meta. The text property is ignored. This meta object will NOT be sent to the translation provider. It will be used to store field meta in the document after the translation is performed and added to the document. Furthermore, the convertText method will be called only for the meta array field. The translated argument will be the array value retrieved from the document, using the valuePath provided in the array meta object. The engine uses apos.util.get(valuePath) under the hood to retrieve the value from the document. The convertText method will add the changed status to the meta object and the engine will store the meta object as field meta in the document:

self.apos.schema.setMeta(doc, '@apostrophecms/automatic-translation', 'myArray', 'data', {
  valuePath: 'myArray',
  type: 'array',
  changed: true
});

The schemaPath property and the eachSchemaField method

The schemaPath property is an array of parent field names that lead to the current field in the schema. It is useful for external modules or project level code that extracts manually document text via the extractText method. The eachSchemaField(schemaPath, schema, callback) method can be used to walk through the schema fields and call a callback on each of them, allowing you to extract additional meta data from the schema fields for a given translation meta item. The callback function is called with two arguments: field and context. The field object is the schema field definition or a widget module. The context.isWidget property is a boolean indicating if the field is a widget object.

Here is an example of how to use it:

// As already mentioned, the `schemaPath` property is an array of parent field names that lead to the current field in the schema. You can grab it from the translation meta object.
const schemaPath = ['main', 'widget:@apostrophecms/rich-text'];
const fieldLabels = [];
self.apos.modules['@apostrophecms-pro/automatic-translation']
  .eachSchemaField(schemaPath, schema, (field, { isWidget }) => {
    // Extract additional meta data from the schema fields
    // for a given translation meta item. In this case, we extract the field labels.
    // Keep in mind in a real world you might want to use `req.t(label)` to translate it.
    fieldLabels.push(
      isWidget ? field.options.label : field.label
    )
  });

Custom Translation Providers

Creating your own translation provider is also supported.

  1. Create a folder modules/my-provider in your project. Add a file index.js with the following content:
module.exports = {
  init(self, options) {
    self.apos.modules['@apostrophecms-pro/automatic-translation']
      .registerProvider(self, {
        name: 'my-provider',
        label: 'My Provider'
      });
  },

  methods(self) {
    return {
      async translate(req, data, sourceLanguage, targetLanguage, options) {
        // Get the text to translate from the `source` language code
        // to the `target` language code
        const texts = data.fields.map(m => m.text);

        // Your translation logic here. Array of translated text (string) is expected
        // with the exact same order (index) as the input text.
        // Your provider should support HTML text. The internal engine expects HTML support and
        // unescapes HTML entities for non-HTML text fields after successful translation.
        const translatedTextArray = self.translateTextWithMyProvider(
          texts,
          sourceLanguage,
          targetLanguage
        );

        // Finally, return the translated text in this format.
        // The `state` can be `translated` or `failed`.
        // If it's `failed`, fields should be empty. It's a good practice to offer
        // a structured logs (self.logError(...)) for the failed translations.
        return {
          state: 'translated',
          fields: translatedTextArray
        };
      },

      async getSupportedLanguages(req, sourceLanguages, targetLanguages) {
        // `sourceLanguages` and `targetLanguages` are optional arrays of language codes.
        // If they are provided, the method should return information about whether
        // each language is supported as a source or a target respectively.
        // if a requested language is not supported, then the supported flag will be false.

        // The expected return format is:
        return {
          source: [
            { code: 'en', supported: true },
            { code: 'fr', supported: true },
            { code: 'zh', supported: false }
          ],
          target: [
            { code: 'en', supported: true },
            { code: 'fr', supported: true },
            { code: 'zh', supported: true }
          ]
        };
      }
    };
  }
};
  1. Add your logic to the code above and configure the module in your app.js file:
require('apostrophe')({
  shortName: 'my-project',
  modules: {
    '@apostrophecms-pro/automatic-translation': {
      options: {
        provider: 'my-provider'
      }
    },
    'my-provider': {}
  }
});

Maximize your global content strategy with these complementary ApostropheCMS Pro modules:

🔍 SEO Assistant

AI-powered meta title and description generation that works with your translated content to optimize search rankings across all languages.

📄 Import/Export Translations

Seamlessly integrate with professional translation services by exporting content for human review and importing polished translations back into your CMS.

🎨 Palette Design Tools

Customize your multilingual sites with visual design tools that maintain brand consistency across all language versions.

Create an account on Apostrophe Workspaces or contact us to learn more about Pro features and build a complete global content management solution.


🏢 Managing Multiple International Sites?

Scaling content across multiple countries and languages? Consider ApostropheCMS Assembly for enterprise multisite management:

✨ Assembly Global Features

  • 🌍 Multi-Region Deployment: Deploy sites across different geographic regions for optimal performance
  • 🏗️ Centralized Translation Management: Coordinate translation workflows across your entire site network
  • 🚀 Shared Content Libraries: Reuse translated assets and content across multiple international sites
  • ⚙️ Per-Site Language Configuration: Different language sets and translation providers per site
  • 📊 Global Analytics: Track content performance and translation effectiveness across regions
  • 🎨 Regional Customization: Adapt designs and content for local markets while maintaining brand consistency

Perfect for international organizations, global brands, or agencies managing content across multiple markets and languages.

Learn more about Assembly or contact our team to discuss your global content strategy.


Made with ❤️ by the ApostropheCMS team. Found this useful? Give us a star on GitHub!

Updated

less than 1 month ago

Version

1.4.3

Report a bug
Pricing