Hooks are little plugins which allow you add further functionality to TranslationStudio. The following hook types are available:
Description | Interface | Hook Type |
---|---|---|
After a successful job creation |
|
|
to process the content of an exported FirstSpirit PAGE data xml file |
|
|
to process the content of an exported FirstSpirit ENTITY data xml file |
|
|
Process custom message xmls |
|
|
Before a translatable file is being imported into a TMS |
|
|
After a translatable XML file has been downloaded from the TMS |
|
|
After a FirstSpirit ImportOperation has been completed |
|
|
TranslationStudio allows you to add custom message fields to the translation workflow. Yet, these fields have to be evaluated in order to do something with them. For example, you may wish to create a meta file based on the selection of a cost centre.
The translation workflow stores your custom fields in an XML format in which the FirstSpirit input component’s name is the node name and the value stored as CDATA
. This tutorial will demonstrate how to implement such a hook.
You will find a Javadoc available at api/javadoc/hooks
First, create a Java project, for example HOOK - Custom Messages and add the library TranslationStudio Connector API jar from your TranslationStudio package at /api/
.
Open the newly created project and create a new class
com.idmedia.translationstudio.hooks.mycustommessage.MyCustomMessageBeforeTmsImportHook implementing ICustomMessageXmlHook
The interface requies you to implement following methods:
/** * Get the name of this hook * @return Name */ @NotNull public String getName() { return "My Custom Message Hook"; } /** * Init this hook * * @param pStorage Storage Object * @param pContext Hook Context */ public void init(IHookChainStorage pStorage, IHookContext pContext) { // TODO } /** * Process Hook * * @param pObject The translatable xml file */ public void onHook(ITranslatableFile pObject) { // TODO }
The method getName()
will be logged before the hook will be called.
The method public void init(IHookChainStorage pStorage, IHookContext pContext)
allows you to process the hook storage values (e.g. if you chain hooks and want to pass results) if necessary. It will be called before onHook(ITranslatableFile pObject)
.
The method public void onHook(ITranslatableFile pObject)
performs the actual task of the hook. It also allows you to access information related to the translatable xml file this hook was called upon.
Since this tutorial demonstrates how to process the custom message values from your TranslationStudio workflow, we need to agree on a sample set of fields. In this example, we will assume the following input components:
For example, edit the TranslationStudio script translationstudio_customfields
and insert the following component:
<CMS_INPUT_RADIOBUTTON name="sc_custom_radio" allowEmpty="no" gridHeight="2" gridWidth="1" hFill="yes" hidden="no" useLanguages="no"> <ENTRIES> <ENTRY value="marketing"> <LANGINFOS> <LANGINFO lang="*" label="Marketing & PR"/> </LANGINFOS> </ENTRY> <ENTRY value="other"> <LANGINFOS> <LANGINFO lang="*" label="Other"/> </LANGINFOS> </ENTRY> </ENTRIES> <LANGINFOS> <LANGINFO lang="*" label="Cost Centre"/> </LANGINFOS> </CMS_INPUT_RADIOBUTTON>
When you execute the TranslationStudio workflow, the new fields can be edited as shown below:
Sample Form.
Your custom input will be stored in the TranslationStudio data source temporarily. Once TranslationStudio has received the translation request, the custom XML will be stored in the TranslationStudio working directory working/custom-messages
in a base64 encoded text file.
<CUSTOM> <sc_custom_radio><![CDATA[marketing]]></sc_custom_radio> <sc_message><![CDATA[Send questions to reply@mycompany.com]]></sc_message> </CUSTOM>
Since we want to create a new file, we need to store the target folder and also store the open FirstSpirit connection in class member fields.
private final static Logger LOGGER = Logger.getLogger(MyCustomMessageBeforeTmsImportHook.class.getName()); private String m_sWorkingDir = null; private IFirstSpiritConnection m_pConnection; /** * Init this hook * * @param pStorage Storage Object * @param pContext Hook Context */ public void init(IHookChainStorage pStorage, IHookContext pContext) { /** * get directory configuration */ m_sWorkingDir = pContext.requestDirectoryConfiguration().getWorkingDirectoryTranslatable(); m_pConnection = pContext.getConnection(); }
Now that we know the target folder and have a FirstSpirit connection at hand, we can start adding functionality to the onInit method.
First, we check if the parameter is valid at all. If it is not, there is no need to proceed:
if (pObject == null) return;
Second, we need to create a StringBuilder
to store all necessary data in:
final StringBuilder psMessage = new StringBuilder(0);
Third, let’s obtain the stored custom XML shown above and process it. It could not be simpler. However, since there might be several pages and entities in the translatable XML file, we will obtain a list of custom XMLs. The evaluation of the XMLs will be encapsulated in a separate method.
final List<String> vsItems = pObject.getCustomFieldValues(m_pConnection); /** * add each message */ for (String _item : vsItems) processXml(_item, psMessage);
Fourth, if there is no XML (i.e. no custom data input), there is no need to proceed.
/**
* No custom message set - no file needs to be stored
*/
if (psMessage.length() == 0)
return;
Fifth, we want to add some header and footer information to the text file we are about to create.
/** * Add Header and Footer to this file */ addCustomHeader(psMessage, pObject); addCustomFooter(psMessage);
Sixth, let’s store the file.
final String sStoredFile = storeNewFile(pObject, psMessage);
if (sStoredFile == null)
{
LOGGER.warning("Could not store custom file.");
return;
}
Finally, since the file has been created, we want to associate it to the translatable xml file. This will guarantee that any connector will also forward this file and consider it a part of the translatable xml file package.
/**
* add to additional files for later use
*/
pObject.addAdditionalFile(sStoredFile);
LOGGER.log(Level.INFO, "Additional information stored in file {0}", sStoredFile);
The example above delegated the processing of a custom XML to a separate method
Importantly, since there might be several pages and entities in a single translatable XML file, we have too associate the respective custom XML to the target section of the translatable XML file. TranslationStudio provides all necessary markers and information in the XML which will look similar to the following example:
<?xml version="1.0" encoding="UTF-8"?> <CUSOM_XML> <CUSTOM_ID>FirstSpiritProjectId-lTranslationStudioContent2Id-lTaskTicketDatasetId</CUSTOM_ID> <CUSTOM> <sc_custom_radio><![CDATA[marketing]]></sc_custom_radio> <sc_message><![CDATA[Send questions to reply@mycompany.com]]></sc_message> </CUSTOM> </CUSOM_XML>
So, since we have all the background knowledge necessary, let us have a look at it using pseudo code
/** * Extract the message content from each message xml * * @param sXml A custom XML * @param psTargetMessage Target StringBuilder to be stored in a file later */ private void processXml(@Nullable String sXml, @NotNull StringBuilder psTargetMessage) { if (sXml == null || sXml.trim().isEmpty()) return; final String psId = extractTag(sXml, "CUSTOM_ID"); final Element pCustomXml = parseXml( extractTag(sXml, "CUSTOM") ); final String sCostCentre = getNodeVale(pCustomXml, "sc_custom_radio"); final String sMessage = getNodeVale(pCustomXml, "sc_message"); /** Store data */ psTargetMessage.append("Cost Centre: ").append(sCostCentre).append("\n"); psTargetMessage.append("Additional Information: ").append(sMessage).append("\n"); }
Finally, let’s add some header and footer information:
/** * Add header information * * @param psMessage * @param pObject */ private void addCustomHeader(@NotNull StringBuilder psMessage, @NotNull ITranslatableFile pObject) { final StringBuilder psHeader = new StringBuilder(); psHeader.append("\n"); psHeader.append("Additional Information\n"); psHeader.append("======================\n"); psHeader.append("\n"); psHeader.append("Source Language: ").append(pObject.getInitialLanguage()).append("\n"); psHeader.append("Target Language: ").append(pObject.getTargetLanguage()).append("\n"); final Date pDate = pObject.getDeadline(); if (pDate != null) { psHeader.append("Deadline: ").append(getFormattedDate(pDate.getTime())); psHeader.append("\n"); } psHeader.append("\n"); psHeader.append("\n"); psMessage.insert(0, psHeader); } /** * Format Date * @param lDate * @return hh:mm:ss */ @NotNull private String getFormattedDate(final long lDate) { if (lDate > 0) try { return new SimpleDateFormat("dd.MM.yyyy").format(new Date(lDate)); } catch (IllegalArgumentException ex) { LOGGER.warning(ex.getMessage()); } return ""; } /** * Add footer information * * @param psMessage */ private void addCustomFooter(@NotNull StringBuilder psMessage) { psMessage.append("Each entity, paragraph or page has an TRANSLATABLE-ID attribute.\n"); psMessage.append("This will allow you to relate additional information to their respective translatable element(s)."); psMessage.append("\n\n- end."); }
Adding your hook to TranslationStudio consists of the following steps:
lib
directory.
conf/hooks.xml
file.
The xml file contains a root node hooks
and you have to add your class name to the respective hook node, for example:
<?xml version="1.0" encoding="UTF-8"?> <hooks> <hook type="custommessagehook"> <class name="com.idmedia.translationstudio.hooks.mycustommessage.MyCustomMessageBeforeTmsImportHook" /> </hook> <hook type="jobcreation" /> <hook type="tmsimport" /> <hook type="processimportablefile" /> <hook type="tmsexport" /> <hook type="fsexporthook_page" /> <hook type="fsexporthook_entity" /> <hook type="fsimporthook" /> </hooks>
Congratulations, you have successfully created your own hook.