Configuring webhook

Setup webhook to automatically import Smartling translations back into Uniform.

Smartling integration settings has Webhook URL input where you need to provide publicly avaiable url where Smartling is going to push completed translation job.

You can find example of implementation in this example.

You would need Smartling integration to be installed and configured. Integration settings page has a Webhook URL settings, which will be passed down as callbackMethod into Smartling's Create job api method

You can find a full example of implementation in this examples repo

Lets create a webhook that can handle composition or entry translations coming back from Smartling.

  1. First we need Smartling and Uniform credentials:

    # Authentication to Smartling SMARTLING_ACCOUNT_ID= SMARTLING_PROJECT_ID= SMARTLING_USER_ID= SMARTLING_USER_SECRET= # Authentication to Uniform UNIFORM_API_KEY= UNIFORM_PROJECT_ID=

Uniform API Keys

Here you can find how to create Uniform API Keys. Smartling integration requires at least Create and Update permissions for Compositions / Entries / Assets

  1. We would also need Uniform and Smartling deps

    npm i @uniformdev/canvas @uniformdev/tms-sdk smartling-api-sdk-nodejs
  2. Smartling Integration is using Job Batches v.2 flow sending JSON files for translations. So we will retrieve translated files back because they also contain some metadata required for us to correcly import back to Uniform Smartling will call our callback with { translationJobUid, type } parameters when Translation Job changes its status. we only care about completed jobs

    const { type, translationJobUid } = req.query; if (type !== 'job.completed') { console.log('We only react on comleted jobs'); res.status(200).send('We only react on comleted jobs'); return; }
  3. Lets retrieve latest completed batch from the job

    const apiBuilder = new SmartlingApiClientBuilder() .setBaseSmartlingApiUrl('https://api.smartling.com') .authWithUserIdAndUserSecret(userId!, userSecret!); const jobsClient = apiBuilder.build(SmartlingJobsApi); const jobBatchesClient = apiBuilder.build(SmartlingJobBatchesApi); const lastBatch = await jobBatchesClient.listBatches( projectId, new ListBatchesParameters({ translationJobUid, limit: 1, status: 'COMPLETED' }) ); const batchInfo = lastBatch.items[0]; const batchDetails = await jobBatchesClient.getBatchStatus(projectId, batchInfo.batchUid);
  4. Now we can load all translated files and start processing them

    const filesClient = apiBuilder.build(SmartlingFilesApi); const translationsStatus: Record<string, boolean> = {}; for (const fileInfo of batchDetails.files) { const downloadParameters = new DownloadFileParameters({ retrievalType: 'published' }); const file = await filesClient.downloadFile( projectId, fileInfo.fileUri, fileInfo.targetLocales[0].localeId, downloadParameters); try { const translationPayload = JSON.parse(file.toString()); const uniformProjectId = translationPayload.metadata.uniformProjectId; const uniformReleaseId = translationPayload.metadata.uniformReleaseId; const uniformEntityType = translationPayload.metadata.entityType; const uniformEntityId = translationPayload.metadata.entity.id; // Now we have everything we need to use tms-sdk package helpers ...
  5. Now lets merge translation into Uniform entities

    const translationPayload = JSON.parse(file.toString()); const uniformProjectId = translationPayload.metadata.uniformProjectId; const uniformReleaseId = translationPayload.metadata.uniformReleaseId; const uniformEntityType = translationPayload.metadata.entityType; const uniformEntityId = translationPayload.metadata.entity.id; const canvasClient = new CanvasClient({ projectId: uniformProjectId, apiKey: uniformAPIKey, bypassCache: true, apiHost: uniformCLIBaseUrl, }); const { translationMerged } = await mergeTranslationToUniform({ canvasClient, contentClient, translationPayload, updateComposition: async ({ canvasClient, composition }) => { await canvasClient.updateComposition(composition); return true; }, onNotFound: ({ translationPayload }) => { const entityType = translationPayload.metadata.entityType; const entityId = translationPayload.metadata.entity.id; console.log(`skip: can not find ${entityType} (${entityId})`); }, onNotTranslatedResult: ({ updated, errorKind, errorText }) => { if (!updated) { console.log('Translation has no updates'); } else if (errorKind !== undefined) { console.warn(errorText || 'Unknown error'); } }, });
  6. If everything goes fine - lets close Smartling job:

    await jobsClient.closeJob(projectId, translationJobUid as string, new CloseJobParameters({})); res.status(200).json('Ok');