Building a custom translation action



The translation action API allows for the addition of custom link elements to be added to the Actions column in the Translations Tab. These UI elements allow users to take specific actions on translation jobs. Often these actions are specific to a certain translation service. Some common use cases include:

  • Canceling a translation in progress
  • Requesting updates for a translation in progress
  • Deep linking to the translation status in a third-party system

If you are building a custom translation service implementation, include translation actions in your TranslationAction classes to allow users to take actions on their translations without having to leave Brightspot. What you can build just depends on what the third-party translation service supports.


To create a new translation action, you need to create a Java class that implements the interface TranslationAction.java.

public class CancelJobTranslationAction implements TranslationAction {

}


The shouldDisplay method is used to determine whether or not to display the TranslationAction to users. This method takes two parameters: the source content for the translation and a list of TranslationActionContext objects that provide information about the translation data the action would be taken on. Each TranslationActionContext can contain the completed translation if the translation is completed, and/or the TranslationLog. Either could be null, but not both.

The shouldDisplay method takes a list of TranslationActionContext objects, because users are presented with the option of taking actions on multiple translation jobs at once. If your action can only be taken on one object at a time, ensure that you check the size of the list is equal to 1.

The method should return true if the action should be displayed, and false otherwise.

@Override
public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {
    return translations.stream()
                .map(TranslationActionContext::getLog)
                .allMatch(log -> log.getService() instanceof CustomTranslationService
                   && TranslationStatus.PENDING.equals(log.getStatus()));
}
  • Ensures that the action only works on Translations using a specific Translation Service.
  • Ensures the action can only be taken on Translations still in the Pending status.



The createActionElement method is used to create an HTML <a> tag that will be displayed to the user if the shouldDisplay method returns true. This method takes the same parameters as shouldDisplay, and should return an AElement object that will be displayed to the user.

There are two common use cases for returning a link element to the user:

  1. To some external third-party site with more information on the translation (for example, if you want to deep link to some status page for that third-party)
  2. A Brightspot page that could either trigger some action or display more information to the user. The page could either be in a new tab or in a pop-up via Brightspot's pop-up system. This implementation is described more in the next step.
    @Override
    public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {
        List<String> logIds = translations.stream()
            .map(TranslationActionContext::getLog)
            .map(TranslationLog::getId)
            .map(UUID::toString)
            .collect(Collectors.toList());
        String url = new UrlBuilder(this.getClass(), p -> p.setLogIds(logIds)).build();
        return A.target("request")
            .href(url)
            .with("Cancel");
    }
  • Creates a URL for another endpoint in the CMS.
  • Creates an AElement to return. The "request" target tells Brightspot to open the request in a pop-up.

Custom translation action (cancel) Custom translation action (cancel)


As mentioned in the previous step, it is often useful to have translation actions hit an endpoint in the CMS to trigger some action.

To accomplish this you can extend ToolPage.java to create a custom endpoint. The below code shows an example ToolPage implementation for our cancel translation job example:

@WebPath("/translation/custom/cancel")
public class CancelTranslationActionToolPage extends ToolPage {

    @WebParameter
    private List<String> logIds;
    
        public void setLogIds(List<String> logIds) {
        this.logIds = logIds;
    }

    private List<LingotekTranslationLog> getTranslationLogs() {
        return Query.from(LingotekTranslationLog.class)
            .where("_id = ?", logIds)
            .selectAll();
    }
    
    @Override
    protected void onGet() throws Exception {
        List<FlowContent> mainContent = new ArrayList<>();
        getTranslationLogs().forEach(log -> {
            CustomTranslationService.cancel(log);                   		
            log.setStatus(TranslationStatus.CANCELED);
            log.save();
        });
        response.toBody().write(
            DIV.className("widget").with(div -> {
                div.add(H1.with("Cancel Translation"));
                div.add(DIV.classList("message", "message-success").with("Success!"));
            }
        ));
    }
}
  • Specifies the path of the endpoint.
  • Defines a URL parameter that is a list of strings.
  • Loops through all Translation Logs provided via the logIds URL parameter and cancels them.
  • Writes a success message in the response.


If you want to configure access to translation permissions through Brightspot's Users & Roles system, you can implement the TranslationPermissionProvider interface. This feature is particularly useful when you need to restrict certain users from performing specific actions related to a translation.

To enable this feature, you need to ensure that your TranslationAction class implements the TranslationPermissionProvider interface and implements the getTranslationPermissions method. This method should return one or more TranslationPermissionValue objects, where each object comprises a unique id (String) and a display name (String).

public class CancelJobTranslationAction implements TranslationAction, TranslationPermissionProvider {

    @Override
    public Set<TranslationPermissionValue> getTranslationPermissions() {
        return Collections.singleton(new TranslationPermissionValue(
            "translation/custom/cancel",
            "Cancel Translation"));
    }

}
  • Unique ID for the permission. This could be any string value, but it is recommended to be descriptive of the permission given.

If you have a ToolPage that is associated with the TranslationAction, make sure to restrict the permission for that ToolPage and apply the @Permission annotation.

@WebPath("/translation/custom/cancel")
@Permission("translation/custom/cancel")
public class CancelTranslationActionToolPage extends ToolPage {
    ...
}


It is important to note that you can combine the TranslationAction, ToolPage, and TranslationAction classes into a single tool page. This means that the example provided above would look like the following:

@WebPath("/translation/custom/cancel")
@Permission("translation/custom/cancel")
public class CancelJobTranslationAction extends ToolPage implements TranslationAction, TranslationPermissionProvider {

    @WebParameter
    private List<String> logIds;
    
    public void setLogIds(List<String> logIds) {
        this.logIds = logIds;
    }

    private List<LingotekTranslationLog> getTranslationLogs() {
        return Query.from(LingotekTranslationLog.class)
            .where("_id = ?", logIds)
            .selectAll();
    }
    
    @Override
    protected void onGet() throws Exception {
        List<FlowContent> mainContent = new ArrayList<>();
        getTranslationLogs().forEach(log -> {
            CustomTranslationService.cancel(log);
            log.setStatus(TranslationStatus.CANCELED);
            log.save();
        });
        response.toBody().write(
            DIV.className("widget").with(div -> {
                div.add(H1.with("Cancel Translation"));
                div.add(DIV.classList("message", "message-success").with("Success!"));
            }
        ));
    }

    @Override
    public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {
        return translations.stream()
            .map(TranslationActionContext::getLog)
            .allMatch(log -> log.getService() instanceof CustomTranslationService
                && TranslationStatus.PENDING.equals(log.getStatus()));
    }

    @Override
    public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {
        List<String> logIds = translations.stream()
            .map(TranslationActionContext::getLog)
            .map(TranslationLog::getId)
            .map(UUID::toString)
            .collect(Collectors.toList());
        String url = new UrlBuilder(this.getClass(), p -> p.setLogIds(logIds)).build();
        return A.target("request")
            .href(url)
            .with("Cancel");
    }

    @Override
    public Set<TranslationPermissionValue> getTranslationPermissions() {
        return Collections.singleton(new TranslationPermissionValue(
            "translation/custom/cancel",
            "Cancel Translation"));
    }
}

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