Skip to main content

Building a translation action

The translation action API allows you to add custom link elements to the Actions column in the Translations tab. These enable users to take specific actions on translation jobs, often actions specific to a particular translation service.

Common use cases

  • Canceling a translation in progress
  • Requesting updates for a pending translation
  • Deep linking to translation status in a third-party system
  • Requesting re-translation of content

Step 1: Implement TranslationAction

Create a class that implements TranslationAction:

1
public class CancelJobTranslationAction implements TranslationAction {
2
3
@Override
4
public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {
5
return true;
6
}
7
8
@Override
9
public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {
10
return null;
11
}
12
}

Step 2: Implement shouldDisplay

The shouldDisplay method determines when your action appears. It receives:

  • source — The source content for the translation
  • translations — List of TranslationActionContext objects with translation data

Each context contains the completed translation (if finished) and/or the TranslationLog.

1
@Override
2
public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {
3
return translations.stream()
4
.map(TranslationActionContext::getLog)
5
// Only show for translations using our custom service
6
.allMatch(log -> log.getService() instanceof TranslationService
7
// Only show for pending translations
8
&& TranslationStatus.PENDING.equals(log.getStatus()));
9
}
tip

Since users can select multiple translations at once, check the list size if your action only works on single items.

Step 3: Implement createActionElement

Create an HTML <a> element that appears when shouldDisplay returns true:

1
@Override
2
public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {
3
List<String> logIds = translations.stream()
4
.map(TranslationActionContext::getLog)
5
.map(TranslationLog::getId)
6
.map(UUID::toString)
7
.collect(Collectors.toList());
8
9
// Create URL for our CMS endpoint
10
String url = new UrlBuilder(CreateActionToolPage.class, p -> p.setLogIds(logIds)).build();
11
12
// Create link that opens in a pop-up
13
return A.target("request").href(url).with("Cancel");
14
}

The element can link to:

  • An external third-party site (e.g., translation status page)
  • A Brightspot page that triggers an action or shows information
  • A pop-up via Brightspot's pop-up system (using target="request")

Step 4: Create a CMS endpoint (optional)

For actions that need to execute logic in Brightspot, create a ToolPage:

1
import java.util.List;
2
3
import com.psddev.cms.ui.ToolPage;
4
import com.psddev.dari.db.Query;
5
import com.psddev.dari.web.annotation.WebParameter;
6
import com.psddev.dari.web.annotation.WebPath;
7
import com.psddev.translation.TranslationLog;
8
import com.psddev.translation.TranslationStatus;
9
10
import static com.psddev.dari.html.Nodes.*;
11
12
@WebPath("/translation/custom/cancel")
13
public class CancelTranslationActionToolPage extends ToolPage {
14
15
@WebParameter
16
private List<String> logIds;
17
18
public void setLogIds(List<String> logIds) {
19
this.logIds = logIds;
20
}
21
22
private List<TranslationLog> getTranslationLogs() {
23
return Query.from(TranslationLog.class)
24
.where("_id = ?", logIds)
25
.selectAll();
26
}
27
28
protected void onGet() throws Exception {
29
// Cancel each translation
30
getTranslationLogs().forEach(log -> {
31
// Cancel with the external service
32
// CustomTranslationService.cancel(log);
33
log.setStatus(TranslationStatus.CANCELED);
34
log.save();
35
});
36
37
// Write success response using CMS Components
38
response.toBody().write(
39
DIV.className("widget").with(div -> {
40
div.add(H1.with("Cancel Translation"));
41
div.add(DIV.classList("message", "message-success")
42
.with("Success!"));
43
})
44
);
45
}
46
}

Key points

  • @WebPath specifies the endpoint URL
  • @WebParameter defines URL parameters
  • onGet() handles the request and writes the response
  • The response can include HTML for display in a pop-up

Step 5: Add permission control (optional)

Implement TranslationPermissionProvider to make access configurable through Brightspot's Users & Roles system:

1
public class CancelJobTranslationActionWithPermissions
2
implements TranslationAction, TranslationPermissionProvider {
3
4
@Override
5
public Set<TranslationPermissionValue> getTranslationPermissions() {
6
return Collections.singleton(new TranslationPermissionValue(
7
// Unique ID for the permission
8
"translation/custom/cancel",
9
// Display name in Users & Roles UI
10
"Cancel Translation"));
11
}
12
13
// ... shouldDisplay and createActionElement implementations

If you have an associated ToolPage, add the @Permission annotation with the same permission ID used in getTranslationPermissions().

Complete example

You can combine TranslationAction, ToolPage, and TranslationPermissionProvider in a single class:

1
2
import java.util.Collections;
3
import java.util.List;
4
import java.util.Set;
5
import java.util.UUID;
6
import java.util.stream.Collectors;
7
8
import com.psddev.cms.ui.Permission;
9
import com.psddev.cms.ui.ToolPage;
10
import com.psddev.dari.db.Query;
11
import com.psddev.dari.db.Recordable;
12
import com.psddev.dari.html.text.AElement;
13
import com.psddev.dari.web.UrlBuilder;
14
import com.psddev.dari.web.annotation.WebParameter;
15
import com.psddev.dari.web.annotation.WebPath;
16
import com.psddev.translation.TranslationLog;
17
import com.psddev.translation.TranslationPermissionProvider;
18
import com.psddev.translation.TranslationPermissionValue;
19
import com.psddev.translation.TranslationService;
20
import com.psddev.translation.TranslationStatus;
21
import com.psddev.translation.page.TranslationAction;
22
import com.psddev.translation.page.TranslationActionContext;
23
24
import static com.psddev.dari.html.Nodes.*;
25
26
@WebPath("/translation/custom/cancel")
27
@Permission("translation/custom/cancel")
28
public class CancelJobTranslationAction extends ToolPage
29
implements TranslationAction, TranslationPermissionProvider {
30
31
@WebParameter
32
private List<String> logIds;
33
34
public void setLogIds(List<String> logIds) {
35
this.logIds = logIds;
36
}
37
38
private List<TranslationLog> getTranslationLogs() {
39
return Query.from(TranslationLog.class)
40
.where("_id = ?", logIds)
41
.selectAll();
42
}
43
44
protected void onGet() throws Exception {
45
getTranslationLogs().forEach(log -> {
46
// Cancel with the external service
47
// CustomTranslationService.cancel(log);
48
log.setStatus(TranslationStatus.CANCELED);
49
log.save();
50
});
51
52
// Write success response using CMS Components
53
response.toBody().write(
54
DIV.className("widget").with(div -> {
55
div.add(H1.with("Cancel Translation"));
56
div.add(DIV.classList("message", "message-success")
57
.with("Success!"));
58
})
59
);
60
}
61
62
@Override
63
public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {
64
return translations.stream()
65
.map(TranslationActionContext::getLog)
66
.allMatch(log -> log.getService() instanceof TranslationService
67
&& TranslationStatus.PENDING.equals(log.getStatus()));
68
}
69
70
@Override
71
public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {
72
List<String> logIds = translations.stream()
73
.map(TranslationActionContext::getLog)
74
.map(TranslationLog::getId)
75
.map(UUID::toString)
76
.collect(Collectors.toList());
77
78
// Create URL using UrlBuilder and return link element
79
String url = new UrlBuilder(CancelJobTranslationAction.class, p -> p.setLogIds(logIds)).build();
80
return A.target("request").href(url).with("Cancel");
81
}
82
83
@Override
84
public Set<TranslationPermissionValue> getTranslationPermissions() {
85
return Collections.singleton(new TranslationPermissionValue(
86
"translation/custom/cancel",
87
"Cancel Translation"));
88
}
89
}

See also