If you customise IBM Maximo Mobile (or Role Based Application), one question comes up at every upgrade: will my customisations survive? In MAS 9.1, IBM's answer is the Migrate Prior Configurations feature that is available in Application Configuration (Maximo Application Framework Tool). It does a job for your application configuration - but there is one category of customisation it does not carry forward. I'll explain that later with some solution proposal.
How this feature works, under the hood
MAS Mobile applications are configured through an application definition - the app.xml presentation file (pages, sections, fields, data sources, lookups, and the like). When you customize an app, the Maximo Application Framework Tool "records your changes" as a delta:
- app.xml - the full application definition.
- app.delta.xml - an automatically generated file that "highlights all configurations applied," i.e. the difference between your app and the IBM original it was based on.
(it also creates delta files for any xml file that are available in the source folder)
Migrate Prior Configurations uses this delta model to move your customisations into a newer IBM baseline. In practice it:
- takes the new IBM base application,
- re‑applies your delta, for example app.delta.xml on top of app.xml
- produces your customised app again on the newer foundation.
The gap: language files are not part of the configuration delta
Here is the important nuance. In MAS Mobile, translations do not live in app.xml. Text in the UI is referenced by a label key; the actual translated strings live in separate static language files shipped inside the app bundle:
<APPID>/public/i18n/labels-sv.json (Swedish)
<APPID>/public/i18n/labels-<lang>.json (one file per language)
At runtime the framework loads the file for the user's language over HTTP (GET ./i18n/labels-<lang>.json) and resolves each label key against it. In other words, the language files are packaged assets, not application configuration - they sit outside the app.xml / app.delta.xml delta model that Migrate Prior Configurations operates on.
The consequence:
- Migrate Prior Configurations does not migrate labels-<lang>.json.
- If you edit IBM's language file directly, your translations are overwritten the next time you refresh or upgrade the base app.
The solution: a custom language‑file
The improvement is simple and fully upgrade‑safe: never edit IBM's language file. Keep your translations in a separate overlay json file, and merge it at runtime. For the purposes of this article, I will focus on the Swedish language (lang code SV).
Put your key/value strings in a separate file, one per language
Alongside IBM's file, add your own:
<APPID>/public/i18n/labels-sv.json IBM's file — never edit (replaced on upgrade)
<APPID>/public/i18n/labels-sv-custom.json your overlay — IBM never touches it
The overlay is a flat JSON of the same shape as IBM's file. A key that matches an IBM key overrides its translation, a new key adds a new string.
Example content to override existing label (labels-sv-custom.json can contains multiple key/values entries). In the Proof of Concept, I customised the Swedish label from the original "Inventarie" to a custom one, "Inventarie TEST".
{
"n8k8m_label": "Inventarie TEST"
}
Merge the overlay at runtime from AppCustomizations.js
AppCustomizations.js is your own customisation file (and is preserved across upgrades). Add a small hook that loads your overlay for the active language and merges it over the framework's in‑memory labels.
import { Localizer } from '@maximo/maximo-js-api';
class AppCustomizations {
async applicationInitialized(app) {
this.app = app;
await this.installCustomLabels();
if (!this.labelsListenerInstalled) {
Localizer.get().on('labels-loaded', () => this.installCustomLabels());
this.labelsListenerInstalled = true;
}
}
async installCustomLabels() {
const lang = (this.app.parseLanguage(this.app.client.userInfo) || 'en')
.toLowerCase().split('-')[0];
const custom = await this.fetchJson(`./i18n/labels-${lang}-custom.json`);
if (custom) Object.assign(Localizer.get().labels, custom);
}
// XHR works on both the browser (Role-Based) and the device build.
fetchJson(url) {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch (e) { resolve(null); } };
xhr.onerror = () => resolve(null);
xhr.send();
});
}
}
export default AppCustomizations;