I18n Library provides multi-language support for OutSystems Reactive Web and Mobile apps through client actions. Translations are loaded from JSON files at runtime — no redeployment needed to add or update languages.
How it works:
App starts → InitTranslation loads default JSON → Screen opens → LoadScreenResource loads screen JSON → Translate expressions render text
/locales/
/locales/ ├── default/ ← shared keys (nav, footer, common) │ ├── en-US.json │ └── ar-SA.json ├── dashboard/ ← screen-specific keys │ ├── en-US.json │ └── ar-SA.json ├── profile/ │ ├── en-US.json │ └── ar-SA.json └── settings/ ├── en-US.json └── ar-SA.json
Rule: The default/ folder is loaded automatically at app start. Screen folders are loaded on-demand.
default/
InitTranslation
LoadScreenResource
Translate
SwitchLocale
SetConfig
GetConfig
Where: Application → OnReady (runs once on app load)
BasePath
TranslationMap
ResourceVersion
"v1.3"
?t=v1.3
Locales
["en-US"]
TranslationMapItem (Record) ├── Key : Text → OutSystems screen name (exact match) └── Value : Text → JSON folder name under basePath
Step 1 — Create TranslationMap list:
// Assign → TranslationMapList ListAppend(TranslationMapList, {Key: "Dashboard", Value: "dashboard"}) ListAppend(TranslationMapList, {Key: "Profile", Value: "profile"}) ListAppend(TranslationMapList, {Key: "Settings", Value: "settings"}) ListAppend(TranslationMapList, {Key: "Transfers", Value: "transfers"})
Step 2 — Create Locales list:
// Assign → LocalesList ListAppend(LocalesList, "en-US") ListAppend(LocalesList, "ar-SA")
Step 3 — Call InitTranslation:
InitTranslation( BasePath: "/MyApp/locales/", TranslationMap: TranslationMapList, ResourceVersion: "v1.3", (optional) Locales: LocalesList )
default/en-US.json
default/ar-SA.json
loadForScreen()
t()
InitTranslation is async — it starts fetching but doesn't block the app. If a screen calls LoadScreenResource before init completes, the library auto-queues and executes after init resolves. No error, no timing issue.
Where: Each Screen → OnReady event
ScreenName
IsLoaded
True
False
// Screen OnReady: LoadScreenResource(ScreenName: "Dashboard") // Then assign: TranslationRevision = TranslationRevision + 1
If ScreenName doesn't exist in TranslationMap, no screen-specific JSON is loaded — but default/ translations are still available. This is not an error.
Where: Any Expression widget — set Function = Yes
Key
"dashboard.title"
DefaultValue
Args
TranslatedText
Translate( "dashboard.title", "Dashboard", "" )
Important: Include CurrentLocale and TranslationRevision as trailing parameters. They aren't used by the function logic — they are ghost dependencies that force OutSystems to re-evaluate the expression when locale changes or translations load.
CurrentLocale
TranslationRevision
JSON:
{ "welcome": "Hello, %{opt1}. You have %{opt2} notifications." }
Expression:
Translate("common.welcome", "Hello", "Gokula,5")
Output: "Hello, Gokula. You have 5 notifications."
"Hello, Gokula. You have 5 notifications."
"Gokula"
"Gokula,5"
"Gokula,5,New"
""
1. Current locale (e.g., ar-SA) → found? → return 2. Parent locale (e.g., ar) → found? → return 3. Default locale (e.g., en-US) → found? → return 4. Parent of default (e.g., en) → found? → return 5. DefaultValue parameter → return 6. Empty string "" → return
Where: Language switcher button/dropdown OnClick
Locale
"ar-SA"
1. SwitchLocale(Locale: "ar-SA") 2. Assign → Client.CurrentLocale = "ar-SA" 3. LoadScreenResource(ScreenName: GetCurrentScreen())
Where: Anywhere at runtime (optional — for advanced use)
DefaultLocale
CacheStrategy
"force-cache"
"no-cache"
"default"
EnableFallback
SetConfig( BasePath: "", ← unchanged ResourceVersion: "v2.0", ← updated Locales: "", ← unchanged DefaultLocale: "", ← unchanged CacheStrategy: "no-cache", ← updated EnableFallback: True ← unchanged )
Where: Debug screens, developer tools
GetConfig() // Use outputs for debug display or conditional logic
Translate("common.save", "Save", "")
// JSON: "Last updated: %{opt1}" Translate("dashboard.lastUpdated", "Last updated", FormatDateTime(Now(), "dd MMM yyyy"))
// JSON: "%{opt1} — %{opt2} on %{opt3}" Translate("dashboard.list.item", "", "John,Transfer,15 Jul")
Translate("premium.feature", If(IsPremiumUser, "Unlock", "Upgrade to access"), "")
// In a Table Records expression: Translate("status." + GetRecordList.Current.Status, GetRecordList.Current.Status, "")
JSON structure:
{ "status": { "active": "نشط", "inactive": "غير نشط", "pending": "قيد الانتظار" } }
// Reusable across screens: LoadScreenResource(ScreenName: GetCurrentScreenName())
{ "dashboard.title": "Dashboard", "dashboard.subtitle": "Your overview", "dashboard.summary.total": "Total", "dashboard.alerts.empty": "No active alerts." }
{ "dashboard": { "title": "Dashboard", "subtitle": "Your overview", "summary": { "total": "Total" }, "alerts": { "empty": "No active alerts." } } }
{ "dashboard": { "title": "Dashboard", "summary.total": "Total" } }
Error: [I18n_Lib] Invalid JSON structure at key: "dashboard.summary.total". Use either pure flat or pure nested format.
[I18n_Lib] Invalid JSON structure at key: "dashboard.summary.total". Use either pure flat or pure nested format.
Cause: LoadScreenResource called before InitTranslation resolved.Fix: Already handled in v1.3.0+ — the library auto-queues. If you still see this error, ensure InitTranslation is in Application OnReady (not Screen OnReady).
Check:
dir
<html>
rtlLocales
ar-SA
ar-EG
he-IL
ur-PK
fa-IR
[dir="rtl"]
This is normal. If you have 5 locales configured but only 2 JSON files for a screen, the missing ones 404 silently. The fallback locale (en-US) is used instead.
en-US
[missing "en-US.some.key" translation]
Cause: Key doesn't exist in any loaded JSON file.Fix: