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