Custom Dynamic Translation build

ganapati2
Tera Contributor

We are trying to build a custom dynamic translation. Currently we are using IBM as our dynamic translator which is a OOB translator. We would like to understand how we could build a custom flow where we are going utilize a third party as a Dynamic Translation service provider. I would like to understand the detailed steps how we could achieve it. Has anyone done this before? Any leads would be much appreciated.

Thanks

Ganapati

 

14 REPLIES 14

HeatherSN
ServiceNow Employee
ServiceNow Employee

@santu889  - You're in luck today; I got a response from our SME here.

For configuring custom third-party translation services using Workflow Studio: when a customer is creating the subflows which are equivalent to "Detect Language [detect_language_v4] and Translate Text [translate_text_v3]", there is a step where there will be a workflow action that will need to be created as well. Using those existing subflows as examples, the Translate Text subflow uses a Translate Text action, and actions like that looks like the best place to map error codes. Actions have an Error Evaluation section where one can, "Add conditions to evaluate errors that may occur in your action's steps at runtime. You can overwrite the default error status that a step produces at runtime by entering new status code and status message values."

WorkflowStudio_TranslateText_action.png

 ------------------
Santu889: when you have a chance, please reply to let us know whether this guidance helped you.  If so, I can have the documentation page updated to include this detailed explanation.




That's good. Thanks for replying. 

MaxMixali
Giga Guru

Building a Custom Dynamic Translation Integration in ServiceNow

Overview:
You can replace the out-of-the-box IBM Dynamic Translation service with any third-party provider (DeepL, Azure Translator, Google Cloud Translation, AWS Translate, etc.). Below are detailed steps and design patterns to implement it.

-------------------------------------------------
0) Choose an Integration Pattern
-------------------------------------------------
A. Pluggable Translator Strategy (Script Include + Outbound REST)
- Create a Translator interface that calls a specific provider implementation (DeepL, Azure, etc.).
- Set the active provider through system properties.

B. IntegrationHub Spoke (Flow-friendly)
- Build custom Spoke actions (Detect Language, Translate Text, Translate Journal).
- Enables Flow Designer orchestration.

-------------------------------------------------
1) Configuration
-------------------------------------------------
System Properties:
- x_company.translator.active = 'deepl'
- x_company.translator.api_key = (encrypted)
- x_company.translator.base_url = endpoint URL
- x_company.translator.default_target_lang = 'en'

Optional Tables:
- u_translation_cache (for caching translations)
- u_translation_job (for batch processing)
- u_translation_glossary (for excluded terms)

-------------------------------------------------
2) Core Server Logic (Strategy + Providers)
-------------------------------------------------
Script Include: Translator (Factory)

var Translator = Class.create();
Translator.prototype = {
initialize: function() {
this.providerName = gs.getProperty('x_company.translator.active', 'deepl');
this.provider = this._factory(this.providerName);
},
translate: function(text, srcLang, tgtLang) {
return this.provider.translate(text, srcLang, tgtLang);
},
detect: function(text) {
return this.provider.detect(text);
},
_factory: function(name) {
switch (name.toLowerCase()) {
case 'azure': return new Translator_Azure();
case 'google': return new Translator_Google();
case 'aws': return new Translator_AWS();
default: return new Translator_DeepL();
}
},
type: 'Translator'
};

Example Provider (DeepL):

var Translator_DeepL = Class.create();
Translator_DeepL.prototype = {
initialize: function() {
this.apiKey = gs.getProperty('x_company.translator.api_key');
this.baseUrl = gs.getProperty('x_company.translator.base_url', 'https://api.deepl.com/v2/');
},
translate: function(text, srcLang, tgtLang) {
var r = new sn_ws.RESTMessageV2();
r.setEndpoint(this.baseUrl + 'translate');
r.setHttpMethod('POST');
r.setRequestHeader('Authorization', 'DeepL-Auth-Key ' + this.apiKey);
r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
r.setRequestBody('text=' + encodeURIComponent(text) +
'&source_lang=' + srcLang.toUpperCase() +
'&target_lang=' + tgtLang.toUpperCase());
var response = r.execute();
var body = response.getBody();
var parsed = JSON.parse(body);
return parsed.translations[0].text;
},
type: 'Translator_DeepL'
};

-------------------------------------------------
3) Where to Call It
-------------------------------------------------
A. Translate Journal/Comments on Create or Update
- Use Business Rules or Flows.
- Example: On insert/update, translate work_notes or comments to user’s language.

B. Dynamic Chat Translation
- Use Messaging Flows or Interceptors to translate inbound/outbound messages.

C. Portal / Widget
- Server script calls Translator.translate() before rendering text.

-------------------------------------------------
4) IntegrationHub Spoke (Flow Implementation)
-------------------------------------------------
1. Create Connection & Credential alias (API key).
2. Build Actions:
- Detect Language
- Translate Text
- Translate Journal Field

Example Action Script:
(function executeAction(inputs, outputs) {
var t = new Translator();
outputs.translatedText = t.translate(inputs.text, inputs.sourceLang, inputs.targetLang);
})(inputs, outputs);

3. Build Flow:
- Trigger: Record Updated (e.g., Incident)
- Action: Translate Text
- Action: Update Record with translation

-------------------------------------------------
5) Security, Compliance, Performance
-------------------------------------------------
- Mask PII before sending text to third-party.
- Store credentials in Credential records, not sys_properties.
- Add caching to reduce cost and latency.
- Handle rate limits, timeouts, and API retries.
- Log provider name and API version for auditability.
- Set language fallbacks and error handling.

-------------------------------------------------
6) Example Flow - Translate Comments to Agent Language
-------------------------------------------------
Trigger: Incident record updated (comments changed)
1. Get agent language (gs.getSession().getLanguage())
2. Translate Text (custom spoke action)
3. Update Record (add to u_translated_notes)

-------------------------------------------------
7) Lessons Learned / FAQs
-------------------------------------------------
- You can keep IBM and fall back to it if your custom provider fails.
- Works in all UIs (Workspace, Portal, Classic) because translation happens server-side.
- Use bulk endpoints for documents or attachments when supported.
- Cache frequent strings to cut cost.
- Batch multiple texts per API call.

-------------------------------------------------
😎 Deliverables You Can Build
-------------------------------------------------
- Script Include: Translator + Provider Implementations
- Outbound REST Connection & Credential
- Flow Designer Spoke: Translate Text
- Flow: Incident Comments Translation
- Optional Cache Table & Business Rule

-------------------------------------------------
Summary
-------------------------------------------------
1. Create configurable provider architecture (Translator Strategy).
2. Implement one or more provider classes (DeepL, Azure, etc.).
3. Expose via GlideAjax, Flow, or REST.
4. Add caching, security, and retry logic.
5. Replace OOB IBM calls with your Translator API.

 

MaxMixali
Giga Guru

ServiceNow: Build a Custom Dynamic Translation Provider (Replace/Extend IBM OOB)

Overview
You can replace or augment the OOB IBM Dynamic Translation with a custom provider (Azure, Google, AWS, DeepL, etc.) without losing the user experience. The safest pattern is a provider-agnostic facade + pluggable providers, exposed to Flow Designer via a Spoke, and callable from BRs/Widgets/Agent Workspace.

High-Level Patterns
A) Facade + Provider Classes (Recommended)
- Script Include 'Translator' (facade) selects a concrete provider based on a sys_property.
- Provider classes (Translator_Azure, Translator_Google, Translator_DeepL, etc.) implement translate() and detect().
- All server-side callers use new Translator().translate(text, 'auto', 'en').

B) IntegrationHub Spoke
- Wrap the facade in Spoke Actions: Translate Text, Detect Language, Translate Journal Field.
- Flow authors can orchestrate translations in Flow Designer UI.

Configuration (Properties/Credentials)
- x_app.translator.active = azure | google | deepl | aws
- x_app.translator.default_target_lang = en
- x_app.translator.base_url = <provider base URL>
- Credentials: Use Connection & Credential aliases or Basic/OAuth profiles (do not store secrets in plain properties).
- Optional: x_app.translator.enabled=true, x_app.translator.rate_limit_per_min, x_app.translator.region (for Azure).

Optional Helper Tables
- u_translation_cache(source_hash, src, tgt, translated, provider, ttl)
- u_translation_glossary(term, replacement, do_not_translate=true/false)

Core Server Logic (Script Includes)
1) Facade
var Translator = Class.create();
Translator.prototype = {
initialize: function() {
var name = (gs.getProperty('x_app.translator.active') || 'azure').toLowerCase();
this.provider = this._factory(name);
},
translate: function(text, srcLang, tgtLang) { return this.provider.translate(text, srcLang, tgtLang); },
detect: function(text) { return this.provider.detect(text); },
_factory: function(name) {
switch(name){
case 'deepl': return new Translator_DeepL();
case 'google': return new Translator_Google();
case 'aws': return new Translator_AWS();
default: return new Translator_Azure();
}
},
type: 'Translator'
};

2) Example Provider (Azure)
var Translator_Azure = Class.create();
Translator_Azure.prototype = {
initialize: function() {
this.key = gs.getProperty('x_app.translator.api_key');
this.url = gs.getProperty('x_app.translator.base_url','https://api.cognitive.microsofttranslator.com/');
this.reg = gs.getProperty('x_app.translator.region','westeurope');
this.defTgt = gs.getProperty('x_app.translator.default_target_lang','en');
},
translate: function(text, src, tgt) {
if (!text) return '';
var body = JSON.stringify([{ "Text": text }]);
var r = new sn_ws.RESTMessageV2();
r.setHttpMethod('POST');
r.setEndpoint(this.url + 'translate?api-version=3.0' + (src ? '&from=' + encodeURIComponent(src) : '') + '&to=' + encodeURIComponent(tgt || this.defTgt));
r.setRequestHeader('Ocp-Apim-Subscription-Key', this.key);
if (this.reg) r.setRequestHeader('Ocp-Apim-Subscription-Region', this.reg);
r.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
r.setRequestBody(body);
var resp = r.execute();
if (resp.getStatusCode() >= 300) { gs.warn('Translation error: ' + resp.getStatusCode() + ' ' + resp.getBody()); return ''; }
var parsed = JSON.parse(resp.getBody());
var out = (parsed && parsed[0] && parsed[0].translations && parsed[0].translations[0].text) || '';
return out;
},
detect: function(text) {
if (!text) return 'auto';
var r = new sn_ws.RESTMessageV2();
r.setHttpMethod('POST');
r.setEndpoint(this.url + 'detect?api-version=3.0');
r.setRequestHeader('Ocp-Apim-Subscription-Key', this.key);
if (this.reg) r.setRequestHeader('Ocp-Apim-Subscription-Region', this.reg);
r.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
r.setRequestBody(JSON.stringify([{ "Text": text }]));
var resp = r.execute();
if (resp.getStatusCode() >= 300) return 'auto';
var parsed = JSON.parse(resp.getBody());
return (parsed && parsed[0] && parsed[0].language) || 'auto';
},
type: 'Translator_Azure'
};

Where to Call It (Common Use Cases)
- Business Rules on task tables: translate new comments/work_notes into agent language.
- Flow Designer: action to translate text or journal deltas on update.
- Agent Workspace/Messaging: translate inbound/outbound messages based on agent/customer language.
- Portal/Widgets: call the facade from server scripts; optionally expose a Scripted REST API for client calls.

Flow Designer (Spoke Actions)
1) Translate Text (inputs: text, srcLang, tgtLang) → outputs.translatedText
(function executeAction(inputs, outputs) {
var t = new Translator();
outputs.translatedText = t.translate(inputs.text, inputs.sourceLang || 'auto', inputs.targetLang || gs.getSession().getLanguage() || 'en');
})(inputs, outputs);

2) Translate Journal Field (inputs: record sys_id, table, field, targetLang)
- Read the latest journal delta only (comments/work_notes).
- Translate via facade.
- Append to a mirror field (e.g., u_translated_notes).

Journal Delta Example (BR after update):
(function executeRule(current, previous) {
var tgt = gs.getSession().getLanguage() || 'en';
var added = current.comments.getJournalEntry(1); // or work_notes
if (!added) return;
var t = new Translator();
var txt = t.translate(added, 'auto', tgt);
if (txt) current.u_translated_notes = (current.u_translated_notes || '') + '\n[→ ' + tgt.toUpperCase() + ']\n' + txt;
})(current, previous);

Security & Compliance
- Use Connection & Credential aliases; avoid plain properties for secrets.
- Mask PII: redact emails/phones/order numbers before sending to provider if required; re-inject after (optional).
- Rate limiting: handle 429/5xx with exponential backoff; add a property x_app.translator.enabled as kill switch.
- Timeout: keep synchronous calls < 5s; for long text, use async jobs/batching.
- Audit: log provider, request id, and status for troubleshooting.

Caching & Glossary (Save Cost/Latency)
- Cache common strings in u_translation_cache keyed by (hash, src, tgt, provider, ttl).
- Maintain a glossary: product names, codes, acronyms → do_not_translate=true.

Coexist with IBM OOB
- Keep IBM as fallback: if custom provider fails, call IBM to avoid agent impact.
- Property x_app.translator.active can switch providers instantly without code changes.

Migration Plan
1) Build Translator facade + one provider (e.g., Azure) in sub-prod.
2) Add Spoke actions; swap one simple use case (e.g., translate Case comments).
3) Add caching/glossary; add retry/backoff.
4) Expand to Agent Chat/Portal; then flip property to make it default.
5) Keep IBM as fallback for a safe cutover.

Troubleshooting Checklist
- 401/403: check credentials; use auth profiles.
- 400/415: wrong Content-Type or request body; JSON.stringify payloads.
- Timeouts: reduce payload size or batch, verify network/MID if used.
- Mixed languages in output: ensure you pass target language consistently; detect only when needed.
- Nothing translates: confirm plugin com.glide.dynamic_translation and ACLs to call SI are in place.

TL;DR
- Implement a Translator facade + provider classes.
- Expose via IntegrationHub Spoke for Flow Designer.
- Secure credentials, add caching/glossary, and fallback to IBM.
- Start with one use case (journal delta translation), then scale.

Hi MaxMixali, 

 

Thanks for the detailed inputs. I have implemented the subflow by imitating the existing IBM flows. It works well. But, as a second approach, we are trying to implement script include (API calls) and call it from a script. I will try your recommendation.