Join the #BuildWithBuildAgent Challenge! Get recognized, earn exclusive swag, and inspire the ServiceNow Community with what you can build using Build Agent.  Join the Challenge.

Alex Coope - SN
ServiceNow Employee
ServiceNow Employee

So, I was in a call with a customer the other day who are going live with NowAssist in Virtual Agent in a bunch of languages and so they had an ask of "how can we translate some of that static strings that aren't coming from the LLM's?".

 

As usual it got me thinking - let's make another LF artifact 🙂

 

Here goes - as usual we need 3 key parts (all made in the global scope). Also, please remember this is a prototype so I can't guarantee it will work for all scenarios or pick up all strings. It will depend on how you've made your various topics, so you may need to adjust the query of the processor script if necessary for your specific use-case.

 

The UI action:
We need to define it on the [sys_cs_context_profile] table

 

AlexCoopeSN_1-1763635827315.png

^ remember to pay attention to the "internal name" of the artifact that is called in the condition of the UI action. We will be calling this one "proto_gen_ai_skill" in this example.

 

The Artifact configuration:

AlexCoopeSN_2-1763635924844.png

 

The processor script (remember to set it for all application scopes):

var LF_gen_ai_skill = Class.create();
LF_gen_ai_skill.prototype = Object.extendsObject(global.LFArtifactProcessorSNC, {
    category: 'localization_framework', // DO NOT REMOVE THIS LINE!

    /**********
     * Extracts the translatable content for the artifact record
     * 
     * @Param params.tableName The table name of the artifact record
     * @Param params.sysId The sys_id of the artifact record 
     * @Param params.language Language into which the artifact has to be translated (Target language)
     * @return LFDocumentContent object
     **********/
    getTranslatableContent: function(params) {
        /**********
         * Use LFDocumentContentBuilder to build the LFDocumentContent object
         * Use the build() to return the LFDocumentContent object
         **********/

        var tableName = params.tableName;
        var sysId = params.sysId;
        var language = params.language;
        var groupName = "";
        var lfDocumentContentBuilder = new global.LFDocumentContentBuilder("v1", language, sysId, tableName);

        // what context profile are we looking at
        var contextProfile = new GlideRecord('sys_cs_context_profile');
        contextProfile.addQuery('sys_id', sysId);
        contextProfile.query();
        if (contextProfile.next()) {
            lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(contextProfile, "ContextProfile", "Name");

            // from the profile we need to find the topics
            var getTopics = new GlideRecord('sys_cs_context_profile_topic');
            getTopics.addQuery('context_profile', contextProfile.sys_id);
            getTopics.query();
            while (getTopics.next()) {
                // we need to process the topics and the ai-skills
				// topics first
				var getCSTopic = new GlideRecord('sys_cs_topic');
				getCSTopic.addQuery('sys_id', getTopics.topic.sys_id);
				getCSTopic.query();
				if (getCSTopic.next()){
					lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(getCSTopic, "Topic", "Name");
					// now we need to get it's corresponding AI Skill
					var getAISkill = new GlideRecord('sys_gen_ai_skill');
					getAISkill.addQuery('skill_document', getCSTopic.sys_id);
					getAISkill.query();
					if (getAISkill.next()){
						lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(getAISkill, "Skill", "Name");
					}
				}
            }

            // we need to process a bunch of static entries from NowAssist
            var defMsgs = [
                "Here's what you can do next:",
                "Search for more results",
                "Need more help?",
                "Sources",
                "Show sources",
                "Hide sources",
                "Ask Now Assist for help or search",
                "Hi {0}, how can I help you",
                "Hi {0}, how can I help you? I'm an AI powered Virtual Assistant that can handle work-related questions and requests.",
                "Generating responses",
                "{0} minute ago",
                "{0} minutes ago",
                "{0} seconds ago",
                "Just now",
                "Helpful",
                "Not helpful",
                "Go to search results",
                "Go to request",
                "New chat",
                "Reply…",
                "Reply to Now Assist…",
                "Copy message",
                "Now Assist",
                "Chats",
                "Support and settings",
                "Some answers generated by AI. Be sure to check for accuracy",
                "upload up to {0} files (max {1} mb each) to ask questions about their contents",
                "Send",
                "Settings",
                "Audio Notifications",
                "Support",
                "No live agents currently available",
                "Active",
                "No chatter at the moment",
                "Updates",
                "a notification has arrived. you can continue the conversation after viewing the notification.",
                "Important updates and reminders",
                "Ongoing chats you can interact with",
				"View AI Steps",
				"Let me look up information related to {0}. for you.",
            ];
            for (var i = 0; i < defMsgs.length; i++) {
                lfDocumentContentBuilder.processString(defMsgs[i].toString(), "Default Messages", "Name");
            }

            // now we need to get some default system messages
            var sysDef = new GlideRecord('sys_properties');
            sysDef.addEncodedQuery('name=com.glide.cs.conversation.interrupt_redirect.message^ORname=com.glide.cs.idle_chat_reminder_message^ORname=com.glide.cs.agent_transfer_msg^ORname=com.glide.cs.autopilot.client_initiated_header^ORname=com.glide.cs.sensitive.data.agent_error^ORname=com.glide.cs.sensitive.data.requester_message^ORname=com.glide.cs.sensitive.data.agent_message^ORname=com.glide.cs.idle_chat_reminder_message_for_agent^ORname=com.glide.cs.autopilot.agent_finished_msg^ORname=com.glide.cs.general.retry_question_message^ORname=com.glide.cs.autopilot.agent_initiated_msg^ORname=com.glide.cs.autopilot.agent_error_msg^ORname=com.glide.cs.autopilot.client_finished_message^ORname=com.glide.cs.autopilot.client_initiated_message^ORname=com.glide.cs.transfer_to_queue_msg^ORname=com.glide.cs.end_chat_msg^ORname=com.glide.cs.conversation_ended_msg^ORname=com.glide.cs.topic_picker_msg^ORname=com.glide.cs.link_account_msg^ORname=com.glide.cs.available_topics_msg^ORname=com.glide.cs.want_something_else_option^ORname=com.glide.cs.wrong_topic_message^ORname=com.glide.cs.general.retry_filtered_list_message^ORname=com.glide.cs.general.waiting_for_live_agent_message^ORname=com.glide.cs.general.retry_max_char_limit_exceeded_message^ORname=com.glide.cs.no_topic_sorry_message^ORname=com.glide.cs.no_topic_found_message^ORname=com.glide.cs.general.live_agent_handoff_message^ORname=com.glide.cs.topic_choice_list_message^ORname=com.glide.cs.topic_confirmation_message^ORname=com.glide.cs.general.live_agent_handoff_error_message^ORname=com.glide.cs.conversation_faulted_reason^ORname=com.glide.cs.chat_conversation_translating_message^ORname=com.glide.cs.linking_account.message^ORname=com.glide.cs.conversation.start_over_message^ORname=com.glide.cs.unlinking_account.message^ORname=com.glide.cs.unlink_account.confirmation_message^ORname=com.glide.cs.resume_conversation.message^ORname=com.glide.cs.general.closing_message^ORname=com.glide.cs.general.chat_survey_verification_message^ORname=com.glide.cs.general.error_message');
            sysDef.query();
            while (sysDef.next()) {
                if (sysDef.value != '') {
                    // it's best to be defensive here
                    lfDocumentContentBuilder.processString(sysDef.value.toString(), "System Messages", "Name");
                }
            }

            // CS profile messages
            var getMsgs = new GlideRecord('sys_cs_context_profile_message');
            getMsgs.addQuery('context_profile', contextProfile.sys_id);
            getMsgs.addEncodedQuery('context_profile.active=true^context_profile.model_type=llm'); // to narrow down the results
            getMsgs.query();
            while (getMsgs.next()) {
                lfDocumentContentBuilder.processString(getMsgs.message.toString(), "Context Message", getMsgs.context_profile.getDisplayValue()); // it needs to be both MSG
                lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(getMsgs, "Context Message", getMsgs.context_profile.getDisplayValue()); // whilst the message field is a TRT field, we might need the translation to also be a TRT
            }
        }

        return lfDocumentContentBuilder.build();
    },

    /**********
     * Uncomment the saveTranslatedContent function to override the default behavior of saving translations
     * 
     * @Param documentContent LFDocumentContent object
     * @return
     **********/
    /**********
        saveTranslatedContent: function(documentContent) {},
    **********/

    type: 'LF_gen_ai_skill'
});

 

The logic of this query is to follow the assistant you've made, then to find all the messages associated to that assistant, then to find all the default messages which are mostly coming from some system properties. If you are finding some strings are still not translated, you may also need to run the specific "topic" artifact to translate the corresponding VA topics you've made.

 

When you have it set up and working, you should see an output like this in the LFTask:

AlexCoopeSN_3-1763636207741.png

 

NOTE: if you want to use this with the Localization Workspace, as usual you will need to define some RCA's and create some Cross Scope privileges for the "execute API" call (to the Script include) and "read" action to the table [sys_cs_context_profile].

 

As usual, because this is a prototype, no support or warranties are offered on this artifact, this is just being shared for the benefit of the community to help you progress faster.

 

So, again we've seen that we can keep expanding the translation capability with more artifacts to help simplify how we translate more areas of the platform.

 

 

 

If you found this helpful / useful, please like, share and subscribe for more as it always helps.

 

 

Version history
Last update:
2 hours ago
Updated by:
Contributors