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-SA → ar → en-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.

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.

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.

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.
// 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" }
};

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:
/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:
{ "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):
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:
SwitchLocale(Locale: "ar-SA")

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:

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

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.