- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
48m ago
So, the mobile app with regards to translations is an area that's been a bit different for a little while now. In that, it's UI level translations (think the text strings that are part of the app itself) have been (and continue to be) packaged in the app you actually install on your device, or are called from the OS of the device itself. Which has both pros and cons associated to it.
In the latest December 2025 release of both the app for Android / iOS, and the app that's installed on the instance side, will change slightly. This update will introduce a new table called [sys_sg_translated] which will hold the sources of a lot of the mobile apps UI level strings.
So, in order to be able to translate the mobile app you will need the following:
- Zurich Patch 4+
- Mobile app - v20.6+
- (new) Mobile Custom Localization (sn_mobile_language)
At first it will enable self-localization of net new languages, but in a future release it will allow for the changing of an ootb translation within the 23 non-English languages we provide today as well.
This means, for the first time (ever) you will be able to translate the Mobile App's UI strings via the Localization Framework or Localization Workspace seamlessly and using your chosen translation method of choice.
- For this to work, you will need to ensure that the App on the devices are up-to-date, and the necessary apps on the instance will also need to be updated in order to get the new logic necessary in the app (such as the table and it's behaviour).
To get you started with that, I've prototyped what an LF Artifact would look like for the SN community 🙂. As usual, the following needs to be created in the Global scope and as usual we need to make a UI action, an LF config record and the Processor script.
The UI Action:
^ In this example, we're going to call the "internal_name" of the artifact "mobile", and it shall be defined on the [sys_sg_native_client] table, as this is the top of the Mobile app's record hierarchy.
The LF Config record:
^ The processor script will be called "LF_Mobile", you will need to follow the "Create Processor Script" wizard as usual.
The Processor Script:
var LF_Mobile = Class.create();
LF_Mobile.prototype = Object.extendsObject(global.LFArtifactProcessorSNC, {
category: 'localization_framework', // DO NOT REMOVE THIS LINE!
/**********
* Extracts the translatable content for the artifact record
*
* params.tableName The table name of the artifact record
* params.sysId The sys_id of the artifact record
* 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);
var getClient = new GlideRecord('sys_sg_native_client');
getClient.addQuery('sys_id', sysId);
getClient.query();
if (getClient.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(getClient, 'Mobile Client', 'Name');
var navBar = new GlideRecord('sys_sg_navigation');
navBar.addQuery('sys_id', getClient.navigation); // it's the value of the Navigation Bar field on the client record
navBar.query();
if (navBar.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(navBar, 'Navigation Bar', 'Name');
// lets now get the items in the navigation bar
var navTabs = new GlideRecord('sys_sg_navigation_tab_map');
navTabs.addQuery('navigation', navBar.sys_id);
navTabs.query();
while (navTabs.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(navTabs, 'Navigation Bar Tabs', 'Name');
// we need to do different loops and checks for different branches that make up the mobile app in question
if (navTabs.navigation_tab.sys_class_name == 'sys_sg_applet_launcher_tab') {
var appletLauncher = new GlideRecord('sys_sg_applet_launcher');
appletLauncher.addQuery('sys_id', navTabs.navigation_tab.applet_launcher);
appletLauncher.query();
if (appletLauncher.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(appletLauncher, 'Applet Launcher', 'Name');
// now we need to follow the applet mapping
var appletLauncherMap = new GlideRecord('sys_sg_applet_launcher_m2m_section');
appletLauncherMap.addQuery('applet_launcher', appletLauncher.sys_id);
appletLauncherMap.query();
while (appletLauncherMap.next()) {
var appUIsection = new GlideRecord('sys_sg_section');
appUIsection.addQuery('sys_id', appletLauncherMap.section);
appUIsection.query();
if (appUIsection.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(appUIsection, 'Applet Sections', 'Name');
}
// there could be more than one in the mapping, now we need to go through each UI section
// records via the "ui section" field, could be in a few different tables, so we need to loop through accordingly
// now we need to check if we have any values in the "screen" field if it's a record for the sys_sg_icon_section table
if (appletLauncherMap.section.sys_class_name == 'sys_sg_icon_section') {
if (appletLauncherMap.section.applets != '') {
// now we need to loop through the sys_id's of the screens and get through each form and all the fields for their labels etc
var screenArr = [appletLauncherMap.section.applets]; // this will build an array that we can split later
var screenChk = screenArr.toString().split(',');
var screen = '';
for (var i = 0; i < screenChk.length; i++) {
screen = this._getScreen(screenChk[i]);
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(screen[i], 'Applet Screens', 'Name');
// now we need to go through the segments
var screenSegments = new GlideRecord('sys_sg_form_segment');
screenSegments.addQuery('form', screen.sys_id);
screenSegments.query();
while (screenSegments.next()) {
var sysScreen = new GlideRecord('sys_sg_screen');
sysScreen.addQuery('sys_id', screenSegments.embedded_screen);
sysScreen.query();
if (sysScreen.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(sysScreen, 'Embedded Screens', 'Name');
}
/*
// now we need to go through fields on these form screens
var screenFields = new GlideRecord('sys_sg_screen_field');
screenFields.addQuery('screen', screenSegments.sys_id);
screenFields.query();
while(screenFields.next()){
// do nothing for now
}
*/
}
}
}
}
}
// we also need to go through the quick actions menu
var actionMap = new GlideRecord('sys_sg_alp_quick_action_map');
actionMap.addQuery('applet_launcher', appletLauncher.sys_id);
actionMap.query();
while (actionMap.next()) {
var buttonInst = new GlideRecord('sys_sg_button_instance');
buttonInst.addQuery('sys_id', actionMap.quick_action_button);
buttonInst.query();
if (buttonInst.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(buttonInst, 'Action Button', 'Name');
}
var buttonFunc = new GlideRecord('sys_sg_button');
buttonFunc.addQuery('sys_id', actionMap.quick_action_button.button);
buttonFunc.query();
if (buttonFunc.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(buttonFunc, 'Button Functions', 'Name');
}
// now we need to get the UI parameter for this button
var buttonParam = new GlideRecord('sys_sg_ui_parameter');
buttonParam.addQuery('button', actionMap.quick_action_button.button.sys_id);
buttonParam.query();
while (buttonParam.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(buttonParam, 'Button Parameters', 'Name');
}
}
}
}
if (navTabs.navigation_tab.sys_class_name == 'sys_sg_settings_tab') {
var appletSettings = new GlideRecord('sys_sg_settings_tab');
appletSettings.addQuery('sys_id', navTabs.navigation_tab);
appletSettings.query();
if (appletSettings.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(appletSettings, 'Applet Settings', 'Name');
}
}
if (navTabs.navigation_tab.sys_class_name == 'sys_sg_applet_tab') {
var appletTab = new GlideRecord('sys_sg_applet_tab');
appletTab.addQuery('sys_id', navTabs.navigation_tab);
appletTab.query();
if (appletTab.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(appletTab, 'Applet Tab', 'Name');
// now we need to go through screens
if (appletTab.screen) {
var appScreen = new GlideRecord('sys_sg_list_screen');
appScreen.addQuery('sys_id', appletTab.screen);
appScreen.query();
if (appScreen.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(appScreen, 'Applet Screen', 'Name');
}
// now we need to loop through screen segments
var screenStreamSegment = new GlideRecord('sys_sg_item_stream_segment');
screenStreamSegment.addQuery('screen', appletTab.screen.sys_id);
screenStreamSegment.query();
while (screenStreamSegment.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(screenStreamSegment, 'Stream', 'Name');
// now we need to loop through the stream M2M segments
var streamM2Msegments = new GlideRecord('sys_sg_item_stream_m2m_segment');
streamM2Msegments.addQuery('segment', screenStreamSegment);
streamM2Msegments.query();
while (streamM2Msegments.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(streamM2Msegments, 'Stream Segments', 'Name');
var streamDataItem = new GlideRecord('sys_sg_data_item');
streamDataItem.addQuery('sys_id', streamM2Msegments.item_stream.data_item);
streamDataItem.query();
if (streamDataItem.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(streamDataItem, 'Stream Data Items', 'Name');
}
// now we need to loop through the stream m2m item configs
var streamM2MitemConfigs = new GlideRecord('sys_sg_item_stream_m2m_master_item');
streamM2MitemConfigs.addQuery('master_item', streamM2Msegments.sys_id);
streamM2MitemConfigs.query();
while (streamM2MitemConfigs.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(streamM2MitemConfigs, 'Stream Item Configs', 'Name');
}
}
}
}
}
}
if (navTabs.navigation_tab.sys_class_name == 'sys_sg_notifications_tab') {
var notifNav = new GlideRecord('sys_sg_notifications_tab');
notifNav.addQuery('sys_id', navTabs.navigation_tab);
notifNav.query();
if (notifNav.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(notifNav, 'Navigation Tabs', 'Name');
}
}
}
}
// now we need to get the static translations
var getSG_trs = new GlideRecord("sys_sg_translated");
getSG_trs.query();
while (getSG_trs.next()) {
lfDocumentContentBuilder.processTranslatableFieldsForSingleRecord(getSG_trs, "Translated Messages", "Name");
}
}
return lfDocumentContentBuilder.build();
},
_getScreen: function(sys) {
var screenCheck = new GlideRecord('sys_sg_screen');
screenCheck.addQuery('sys_id', sys);
screenCheck.query();
if (screenCheck.next()) {
return screenCheck;
}
},
/**********
* Uncomment the saveTranslatedContent function to override the default behavior of saving translations
*
* documentContent LFDocumentContent object
* @return
**********/
/**********
saveTranslatedContent: function(documentContent) {},
**********/
type: 'LF_Mobile'
});
VERY IMPORTANT NOTE: This artifact will error and fail, or not be usable if your Mobile apps on your instance(s) are not updated to
at least the December 2025 version, and if the Apps on the device is not updated they will not show the translation changes or
additions you make. So be sure to update everything first.
So, when you generate the task, you should see the following sections:
To make it easier to see, I've collapsed all but the last one. The idea is that the artifact picks up all of the elements that make up the various areas of the mobile app in question plus the new "translated messages".
What this artifact will do:
- Allow for the translation of app level UI strings into net-new languages (existing language changes in a future release)
What this artifact will not do:
- It won't translate the entire experience of the mobile app because some elements come from:
- Catalog items / Record Producers -> use the Catalog Item artifact as usual
- KB articles -> use the KB article artifact as usual
- VA Topics -> use the VA topic's artifact as usual
- etc
This artifact will be updated soon with some other exciting new enhancements to the Localization Framework which I will be talking about in another post very soon, but for now it is still considered a "prototype".
For the time being, this artifact is provided as a "prototype" without any warranties or support provided.
If you found this useful, please like, share and subscribe for more as it always helps.
