OutSystems ships a multilingual feature, but it bakes every translation into the module at publish time — add a language or fix a typo and you redeploy. I wanted translations that live in JSON files and load at runtime, so anyone can drop in fr-FR.json and the app picks it up without a rebuild. That's what the i18n-o11 component on the Forge does.

It's about 1.7 KB of core JavaScript. Translation JSON loads lazily, per screen. Missing keys walk a fallback chain (ar-SAaren-US) so the UI never renders blank. RTL flips automatically — including for ur-PK, he-IL, and fa-IR, which OutSystems does not treat as right-to-left on its own. As of 1.5.3 it initializes itself from a config file — no OnApplicationReady wiring — and it remembers the last language across a reload instead of flashing English first. Here's the whole path from Forge to a working multilingual screen.

1. Find it on the Forge

It's here: outsystems.com/forge/component-overview/25301/i18n-o11 — the component by Gokula Kannan P, lightweight runtime i18n with lazy per-screen JSON. Or just search the Forge for i18n.

Forge search results for i18n

2. Download and install in Service Studio

Open the listing and hit Try now, or Download to install it into your environment. The current stable version is 1.5.3, compatible with OutSystems 11 Reactive.

I18n Forge listing page

3. Reference the actions you need

In your app module, open Manage Dependencies, select I18n_Lib, and check the public elements you'll use. Day to day you need just two:

  • Translate — the expression that returns translated text
  • SwitchLocale — changes the active language

Take LoadScreenResource for lazy per-screen loading, plus GetLocale and the I18nLib script. InitTranslation still exists for manual setup, but 1.5.3 lets you skip it — see the next step.

Manage Dependencies — selecting I18n_Lib actions

4. Configure once with i18n.config.js

Older versions had you call InitTranslation in OnApplicationReady and build the config as OutSystems Lists. 1.5.3 drops that. Deploy a single i18n.config.js at your app root — served at /<YourModule>/i18n.config.js — and the library fetches it on load, constructs the engine, and initializes itself.

js
// i18n.config.js  →  served at /I18nDemo/i18n.config.js
return {
    basePath: "/I18nDemo/locales/",
    locales:  ["en-US", "ar-SA", "ur-PK", "fr-FR", "zh-CN"],
    rtlLocales: ["ar-SA", "ar-EG", "he-IL", "ur-PK", "fa-IR"],
    translationMap: { "home": "home", "dashboard": "dashboard" }
};

i18n.config.js deployed as an app resource

The file is fetched with no-cache, so editing it takes effect on the next reload — no cache-buster to bump. And before it initializes, it reads the locale OutSystems already persisted ($…$CurrentLocale in localStorage) and starts there, so a reload comes up in Arabic instead of painting English first and swapping.

The JSON sits under basePath like this:

CODE
/I18nDemo/locales/
  default/        (loaded at app start)
    en-US.json
    ar-SA.json
  dashboard/      (loaded per screen)
    en-US.json
    ar-SA.json

Each file is flat or nested JSON, with %{opt1} placeholders for interpolation:

json
{ "welcome": "Welcome, %{opt1}", "notifications": "You have %{opt1} notifications." }

You only ship files for locales you actually translated — a missing fr-FR.json 404s silently and falls back.

5. Translate text with an expression

Anywhere you render text, use a Translate expression (set Function = Yes):

CODE
Translate("home.welcome", "Welcome", UserName)

Key, then a DefaultValue shown if the key is missing, then comma-separated Args that fill %{opt1}, %{opt2}, and so on. There is no revision variable and no ghost dependency to pass — the component re-renders these expressions when a screen's JSON finishes loading or the locale changes. On first paint you briefly see the DefaultValue, then it swaps to the translation.

6. Switch language with SwitchLocale

Wire a language button to a client action that calls SwitchLocale, passing the target locale as its input:

CODE
SwitchLocale(Locale: "ar-SA")

SwitchLocale action with Locale input

That one call sets the active locale, applies text direction, and re-renders the screen. Nothing else to do — no manual Assign, no direction toggling in your own JavaScript.

See it live

There's a running demo with en-US, ar-SA, ur-PK, fr-FR, and zh-CN:

https://developergeekay.outsystemscloud.com/I18nDemo/

English renders left-to-right:

Demo screen in English, left-to-right

Switch to Arabic and the entire layout mirrors to right-to-left — navigation, buttons, and text:

Demo screen in Arabic, right-to-left

That's the full loop: install from Forge, reference two actions, drop in a config file, translate with an expression, switch with one call. After that, translations are just JSON files — add a language by adding a folder, no redeploy.