Building a translation service integration



Brightspot's Translation Service API provides a standardized interface for translating content stored in Brightspot. To use this API, you must implement a custom translation service that integrates with a third-party translation provider by communicating with external APIs, such as a REST API.

This topic provides guidance on how to implement a custom translation service integration. Note that since each integration can be quite different depending on the third-party service, this topic may not cover all details needed to build an integration with a specific third-party.


The following table lists the dependencies for including translation services in your build. The Translation dependency is required, and then include the dependencies for individual services your build supports.

Dependency Group ID Artifact ID
Translation (mandatory) com.psddev translation
Translation: AWS com.psddev translation-aws
Translation: Google com.psddev translation-google
Translation: Lingotek com.psddev translation-lingotek


The TranslationService abstract class is designed to represent a service that handles translations of content. To implement a new translation service, you need to create a new class that extends the TranslationService abstract class and implements its methods.

A translation service defines both the data the user needs to input to complete the translation (covered in Step 2) and the business logic to begin the translation (covered further on).

Below is an example implementation of a start to a translation service called Pig Latin Translation Service. Note that the implementation changes the display name in the Brightspot UI to just "Pig Latin:"

@Recordable.DisplayName("Pig Latin")
public class PigLatinTranslationService extends TranslationService {
    
    //Implementation expanded in later steps
}
  • Changes the display name in the Brightspot UI to just "Pig Latin."


When a user is requesting a translation for a piece of content, the first step is to choose what translation service they want to use.

In some cases, it can be helpful or necessary for the user to make additional configurations for a specific service. For example, a translation service might require a translation to be associated with a specific project in the translation system.

To achieve this, add fields to the TranslationService class and annotate them with any required UI elements or validations. The translation system will present these fields to the user when choosing a translation service based on common Brightspot data modeling concepts. Any data input in these fields by users will be available when the translation begins.

Below, the Pig Latin Translation Service is expanded with a set of excluded vowels as a configuration field:

package com.example.translation;

import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

import com.psddev.translation.TranslationContext;
import com.psddev.translation.TranslationLog;
import com.psddev.translation.TranslationService;

public class PigLatinTranslationService extends TranslationService {
    
    @Values({ "a", "e", "i", "o", "u" })
    private Set<String> excludedVowels;
}

Translation Service Selection Translation Service Selection


Each translation service might support translating from a different set of source locales. To handle this, each TranslationService must provide a distinct set of locales that the service supports via the getSupportedLocales(Locale sourceLocale) method. If the service only supports translating to some target locales from a specific set of source locales, you have access to the source locale.

By implementing this method, the CMS user will only see the set of supported locales when they are presented with the option to pick target locales.

The list of supported locales can be hard-coded or retrieved from some external location, such as via an API call to the translation service.

Below is an implementation of getSupportedLocales for the Pig Latin Translation Service that ensures it only allows translating from English to Pig Latin:

@Override
public Set<Locale> getSupportedLocales(Locale sourceLocale) {
    if (sourceLocale.getLanguage().equals("en")) {
        return Stream.of(Locale.forLanguageTag("la"), Locale.US).collect(Collectors.toSet());
    }
    return null;
}


In some situations, you might want to pre-select some target locales for users when they are requesting translations. This might be because there are some defaults that should always be pre-selected, or the target locales are pre-defined in some way (in this situation, Step 5 is also useful).

The translation service allows for this via the method getDefaultLocales(). Any locales returned by this method will be pre-selected for the user in the target locale selection step.

@Override
public Set<Locale> getDefaultLocales() {
    return Collections.singleton(Locale.forLanguageTag("la"));
}


Some translation services do not allow for one-off target locale selections. For example, it is somewhat common for the target locales to be defined by which project the translation is a part of. In this case, you do not want the user to be able to choose target locales per translation. In this situation, you can use the method allowTargetLocaleSelection() together with the getDefaultLocales() method to show users a read-only list of target locales, rather than the standard pick list.

@Override
public boolean allowTargetLocaleSelection() {
    return false;
}

Translation Target Locale Selection Translation Target Locale Selection


In some cases, it might be necessary to implement custom logic for transforming the content stored in Brightspot to what is sent to the translation service and how the data received back from the translation service is applied back to Brightspot content models.

This can be done by implementing a custom TranslationDataConverter.

In a concrete TranslationService you can ensure your translation service always uses a specific data converter by returning it in the getDataConverter() method.

By default, the system uses a provided standard data converter. This generic converter should work for most use cases.


To initiate the translation process for the provided context, you need to implement the translate method. This method will differ depending on the translation service you are using. Some services require simple API calls to translate the content synchronously, while others require an asynchronous process where the content is sent out for translation, and Brightspot waits for an indeterminate amount of time to receive the translation back.

You have access to the full Brightspot and Java ecosystem, including repeating tasks and custom endpoints, to facilitate the implementation of your translation logic.

The translate method provides the following context details to initiate the translation:

  • The site from which the translation was requested
  • The user who requested the translation
  • The locale of the content being translated
  • The target locales requested
  • One or more TranslationContextItem objects, each representing a separate piece of content that is being translated. These objects provide the following details:
    • The source content
    • The source draft (can be null)
    • The source overlay (can be null)
    • The translation data that should be translated (produced by TranslationDataConverter)
    • The translation service settings that should be used
    • The raw source data

The translate method does not need to complete the translation, but at a minimum it must instantiate and save a number of TranslationLog instances to track progress. The number of logs returned should be equal to the number of locales in the provided context.

A TranslationLog is a record of each translation. For each log, you can track:

  1. The translation service used
  2. The ID of the source content
  3. The draft ID of the source (if the translation is for a revision)
  4. The overlay ID of the source (if the translation is for an overlay)
  5. The target locale
  6. The site that the translation was requested from
  7. The user who requested the translation
  8. The date the translation was requested
  9. The status of the translation (SUCCESS, SUCCESS_WITH_ERRORS, PENDING, FAILURE, or CANCELED)
  10. A hash of the source content that is being translated
  11. The source data that was translated
  12. A message
  13. The translated data
  14. The ID of the translated content in Brightspot

Ensure that you set the values for items 1–11 above in your translate method.

If you want to complete the translation in the translate method, call TranslationService#completeTranslation for each TranslationLog. This method takes the translated data, creates a new piece of content with that data in Brightspot, relates it to the source content, and completes the translation with the appropriate TranslationCompletionAction.

If you do not want to complete the translation yet, create your translation logs with a status of PENDING, and then call the completeTranslation method when you receive the completed data returned from the service.

Below is a completed version of the Pig Latin Translation Service, which demonstrates how to bring all the pieces together.

package com.psddev.translation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class PigLatinTranslationService extends TranslationService {

    // Pig Latin vowels
    private static final Set<Character> VOWELS = new HashSet<>();

    static {
        VOWELS.add('a');
        VOWELS.add('e');
        VOWELS.add('i');
        VOWELS.add('o');
        VOWELS.add('u');
    }

    @Values({ "a", "e", "i", "o", "u" })
    private Set<String> excludedVowels;

    @Override
    public Set<Locale> getSupportedLocales(Locale sourceLocale) {
        if (sourceLocale.getLanguage().equals("en")) {
            return Stream.of(Locale.forLanguageTag("la"), Locale.US).collect(Collectors.toSet());
        }
        return null;
    }

    @Override
    public Set<Locale> getDefaultLocales() {
        return Collections.singleton(Locale.forLanguageTag("la"));
    }

    @Override
    public boolean allowTargetLocaleSelection() {
        return false;
    }

    /**
     * Initiates translation for the provided {@code context} by translating the provided text into Pig Latin.
     *
     * @param context The {@link TranslationContext} object containing the text to translate.
     * @return A {@code List} of {@link TranslationLog} objects representing the translation results.
     */
    @Override
    public List<TranslationLog> translate(TranslationContext context) {

        List<TranslationLog> logs = new ArrayList<>();

        for (TranslationContextItem contextItem : context.getItems()) {

            for (Locale targetLocale : context.getTargetLocales()) {
                TranslationLog log = new TranslationLog();
                log.setService(this);
                log.setSource(contextItem.getSource());
                log.setDraft(contextItem.getDraft());
                log.setOverlay(contextItem.getOverlay());
                log.setLocale(targetLocale);
                log.setSite(context.getSite());
                log.setUser(context.getUser());
                log.setTranslationDate(new Date());
                log.setContentHash(contextItem.getTranslationData().hashCode());
                log.setSourceData(contextItem.getSourceData());

                TranslationData output = null;
                output = translateData(contextItem.getTranslationData());
                completeTranslation(log, output);
                logs.add(log);
            }
        }
        return logs;
    }

    private TranslationData translateData(TranslationData data) {
        Map<String, TranslationValue> outputData = new LinkedHashMap<>();
        for (String key : data.getData().keySet()) {
            TranslationValue value = data.getData().get(key);
            if (value instanceof TextTranslationValue) {
                outputData.put(
                    key,
                    new TextTranslationValue(
                        ((TextTranslationValue) value).getType(),
                        translateString(((TextTranslationValue) value).getText())));
            } else if (value instanceof RichTextTranslationValue) {
                List<RichTextType> richTextOutput = new ArrayList<>();
                for (RichTextType richTextType : ((RichTextTranslationValue) value).getValues()) {
                    if (richTextType instanceof TextTranslationValue) {
                        richTextOutput.add(new TextTranslationValue(
                            ((TextTranslationValue) richTextType).getType(),
                            translateString(((TextTranslationValue) richTextType).getText())));
                    } else if (richTextType instanceof TranslationData) {
                        richTextOutput.add(translateData((TranslationData) richTextType));
                    }
                }
                outputData.put(key, new RichTextTranslationValue(richTextOutput));
            }
        }
        return new TranslationData(outputData);
    }

    /**
     * Translates the provided {@link String} into Pig Latin.
     *
     * @param input The {@link String} to translate.
     * @return The translated {@link String}.
     */
    private String translateString(String input) {

        String[] words = input.split("\\s+");
        StringBuilder resultBuilder = new StringBuilder();

        for (String word : words) {
            char firstChar = word.charAt(0);
            if (getSupportedVowels().contains(Character.toLowerCase(firstChar))) {
                // If the word starts with a vowel, just append "yay"
                resultBuilder.append(word).append("yay");
            } else {
                // Otherwise, move the first consonant(s) to the end and append "ay"
                int index = 1;
                while (index < word.length() && !getSupportedVowels().contains(Character.toLowerCase(word.charAt(index)))) {
                    index++;
                }
                resultBuilder.append(word.substring(index)).append(word.substring(0, index)).append("ay");
            }
            resultBuilder.append(" ");
        }

        // Remove trailing space
        if (resultBuilder.length() > 0) {
            resultBuilder.setLength(resultBuilder.length() - 1);
        }

        return resultBuilder.toString();
    }

    private Set<Character> getSupportedVowels() {
        return VOWELS
            .stream()
            .filter(vowel -> excludedVowels == null || !excludedVowels.contains(vowel.toString()))
            .collect(Collectors.toSet());
    }
}

Our robust, flexible Design System provides hundreds of pre-built components you can use to build the presentation layer of your dreams.

Asset types
Module types
Page types
Brightspot is packaged with content types that get you up and running in a matter of days, including assets, modules and landing pages.

Content types
Modules
Landing pages
Everything you need to know when creating, managing, and administering content within Brightspot CMS.

Dashboards
Publishing
Workflows
Admin configurations
A guide for installing, supporting, extending, modifying and administering code on the Brightspot platform.

Field types
Content modeling
Rich-text elements
Images
A guide to configuring Brightspot's library of integrations, including pre-built options and developer-configured extensions.

Google Analytics
Shopify
Apple News