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:
1public class CancelJobTranslationAction implements TranslationAction {23@Override4public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {5return true;6}78@Override9public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {10return null;11}12}
Step 2: Implement shouldDisplay
The shouldDisplay method determines when your action appears. It receives:
source— The source content for the translationtranslations— List ofTranslationActionContextobjects with translation data
Each context contains the completed translation (if finished) and/or the TranslationLog.
1@Override2public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {3return translations.stream()4.map(TranslationActionContext::getLog)5// Only show for translations using our custom service6.allMatch(log -> log.getService() instanceof TranslationService7// Only show for pending translations8&& TranslationStatus.PENDING.equals(log.getStatus()));9}
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@Override2public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {3List<String> logIds = translations.stream()4.map(TranslationActionContext::getLog)5.map(TranslationLog::getId)6.map(UUID::toString)7.collect(Collectors.toList());89// Create URL for our CMS endpoint10String url = new UrlBuilder(CreateActionToolPage.class, p -> p.setLogIds(logIds)).build();1112// Create link that opens in a pop-up13return 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:
1import java.util.List;23import com.psddev.cms.ui.ToolPage;4import com.psddev.dari.db.Query;5import com.psddev.dari.web.annotation.WebParameter;6import com.psddev.dari.web.annotation.WebPath;7import com.psddev.translation.TranslationLog;8import com.psddev.translation.TranslationStatus;910import static com.psddev.dari.html.Nodes.*;1112@WebPath("/translation/custom/cancel")13public class CancelTranslationActionToolPage extends ToolPage {1415@WebParameter16private List<String> logIds;1718public void setLogIds(List<String> logIds) {19this.logIds = logIds;20}2122private List<TranslationLog> getTranslationLogs() {23return Query.from(TranslationLog.class)24.where("_id = ?", logIds)25.selectAll();26}2728protected void onGet() throws Exception {29// Cancel each translation30getTranslationLogs().forEach(log -> {31// Cancel with the external service32// CustomTranslationService.cancel(log);33log.setStatus(TranslationStatus.CANCELED);34log.save();35});3637// Write success response using CMS Components38response.toBody().write(39DIV.className("widget").with(div -> {40div.add(H1.with("Cancel Translation"));41div.add(DIV.classList("message", "message-success")42.with("Success!"));43})44);45}46}
Key points
@WebPathspecifies the endpoint URL@WebParameterdefines URL parametersonGet()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:
1public class CancelJobTranslationActionWithPermissions2implements TranslationAction, TranslationPermissionProvider {34@Override5public Set<TranslationPermissionValue> getTranslationPermissions() {6return Collections.singleton(new TranslationPermissionValue(7// Unique ID for the permission8"translation/custom/cancel",9// Display name in Users & Roles UI10"Cancel Translation"));11}1213// ... 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:
12import java.util.Collections;3import java.util.List;4import java.util.Set;5import java.util.UUID;6import java.util.stream.Collectors;78import com.psddev.cms.ui.Permission;9import com.psddev.cms.ui.ToolPage;10import com.psddev.dari.db.Query;11import com.psddev.dari.db.Recordable;12import com.psddev.dari.html.text.AElement;13import com.psddev.dari.web.UrlBuilder;14import com.psddev.dari.web.annotation.WebParameter;15import com.psddev.dari.web.annotation.WebPath;16import com.psddev.translation.TranslationLog;17import com.psddev.translation.TranslationPermissionProvider;18import com.psddev.translation.TranslationPermissionValue;19import com.psddev.translation.TranslationService;20import com.psddev.translation.TranslationStatus;21import com.psddev.translation.page.TranslationAction;22import com.psddev.translation.page.TranslationActionContext;2324import static com.psddev.dari.html.Nodes.*;2526@WebPath("/translation/custom/cancel")27@Permission("translation/custom/cancel")28public class CancelJobTranslationAction extends ToolPage29implements TranslationAction, TranslationPermissionProvider {3031@WebParameter32private List<String> logIds;3334public void setLogIds(List<String> logIds) {35this.logIds = logIds;36}3738private List<TranslationLog> getTranslationLogs() {39return Query.from(TranslationLog.class)40.where("_id = ?", logIds)41.selectAll();42}4344protected void onGet() throws Exception {45getTranslationLogs().forEach(log -> {46// Cancel with the external service47// CustomTranslationService.cancel(log);48log.setStatus(TranslationStatus.CANCELED);49log.save();50});5152// Write success response using CMS Components53response.toBody().write(54DIV.className("widget").with(div -> {55div.add(H1.with("Cancel Translation"));56div.add(DIV.classList("message", "message-success")57.with("Success!"));58})59);60}6162@Override63public boolean shouldDisplay(Recordable source, List<TranslationActionContext> translations) {64return translations.stream()65.map(TranslationActionContext::getLog)66.allMatch(log -> log.getService() instanceof TranslationService67&& TranslationStatus.PENDING.equals(log.getStatus()));68}6970@Override71public AElement createActionElement(Recordable source, List<TranslationActionContext> translations) {72List<String> logIds = translations.stream()73.map(TranslationActionContext::getLog)74.map(TranslationLog::getId)75.map(UUID::toString)76.collect(Collectors.toList());7778// Create URL using UrlBuilder and return link element79String url = new UrlBuilder(CancelJobTranslationAction.class, p -> p.setLogIds(logIds)).build();80return A.target("request").href(url).with("Cancel");81}8283@Override84public Set<TranslationPermissionValue> getTranslationPermissions() {85return Collections.singleton(new TranslationPermissionValue(86"translation/custom/cancel",87"Cancel Translation"));88}89}
See also
- Translation Services — Building custom translation integrations
- Completion Actions — Customizing post-translation behavior