i18n
Reactive icon

I18n

Stable version 1.1.8 (Compatible with OutSystems 11)
Uploaded
 on 8 Aug
 by 
0.0
 (0 ratings)
i18n

I18n

Documentation
1.1.8

1. Overview

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

Automatic re-render (new): you no longer need a TranslationRevision client variable or any “ghost dependency”. Translations refresh on screen automatically when a JSON file finishes loading or when the locale changes. See Automatic Re-render.


2. Key Features

  • Runtime JSON translations — add or edit languages without redeploying the app.
  • Lazy, per-screen loading — each screen fetches only the JSON it needs.
  • Automatic re-render — no client variable, no ghost dependency; expressions refresh themselves.
  • Right-to-left (RTL) support — including locales OutSystems does not ship natively (ur-PK, he-IL, fa-IR).
  • Fallback chain — locale → parent → default, with graceful handling of missing files.
  • Interpolation & pluralization%{opt1} placeholders and count-based forms.
  • CDN or app-resource hosting — relative or absolute base paths.
  • Validation-message translation and optional data-trans HTML auto-translation.
  • Low footprint — core + wrapper are roughly 3–4 KB gzipped combined.


3. Prerequisites

ItemDetails
PlatformOutSystems 11+ Reactive Web (or Mobile)
Module dependencyReference I18n_Lib in your application
JSON filesHosted under a /locales/ folder (app resources or CDN)


4. File Structure

/locales/
├── default/              ← shared keys (nav, footer, common) — loaded at app start
│   ├── en-US.json
│   └── ar-SA.json
├── dashboard/            ← screen-specific keys — loaded on demand
│   ├── 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. You only need JSON files for locales you actually translated — missing files 404 silently and fall back to the default locale.


5. Client Actions Reference

Client ActionWhere to CallPurpose
InitTranslationApplication OnReadyInitialize library, load default translations
LoadScreenResourceScreen OnReadyLoad screen-specific translations
TranslateExpressions (Function = Yes)Get translated text by key
SwitchLocaleLocale switcher buttonChange active language (syncs OS locale, applies RTL, re-renders)
TranslateValidationMessagesAfter SwitchLocaleTranslate built-in validation messages
ObserveDOM / DisconnectObserverScreen OnReady / OnDestroyAuto-translate data-trans HTML elements
SetConfig / GetConfigAnywhere (optional)Override or read config at runtime

6. InitTranslation

Where: Application → OnReady (runs once on app load)

Input Parameters

ParameterTypeMandatoryDescription
BasePathText✅ YesURL to the /locales/ folder. Relative or absolute (CDN). Must end with /.
TranslationMapTranslationMapItem ListNoMaps screen names → JSON folder names
ResourceVersionTextNoCache-bust string (e.g., "v1.5"). Appended as ?t=v1.5
LocalesText ListNoLocale codes to preload. Empty = ["en-US"]
DefaultLocaleTextNoFallback locale. Empty = "en-US"

TranslationMapItem Structure

TranslationMapItem (Record)
├── Key   : Text    → OutSystems screen name (case-insensitive)
└── Value : Text    → JSON folder name under basePath

How to Build

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"})

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.5",   (optional)
    Locales:          LocalesList
)

Timing Note

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.


7. LoadScreenResource

Where: Each Screen → OnReady event

Input Parameters

ParameterTypeMandatoryDescription
ScreenNameTextNoScreen name (matches a TranslationMap key). Supports comma-separated names. Empty + empty map → auto-detects the current screen.

Output Parameters

ParameterTypeDescription
IsLoadedBooleanTrue if at least the default-locale file loaded. False only if the default locale also failed.

How to Use

// Screen OnReady — that's all. No revision variable to increment.
LoadScreenResource(ScreenName: "Dashboard")

Do not block render on it. The screen paints immediately with each Translate call's DefaultValue, then updates automatically the instant the JSON arrives.

Behaviour

ScenarioIsLoadedWhat Renders
en-US.json + ar-SA.json both foundTrueTranslated text
Only en-US.json found (ar-SA 404)Trueen-US text (fallback)
All files 404FalseDefaultValue from Translate
Screen not in TranslationMapTrueOnly default/ keys available

8. Translate (Expression)

Where: Any Expression widget — set Function = Yes

Input Parameters

ParameterTypeMandatoryDescription
KeyText✅ YesTranslation key (e.g., "dashboard.title")
DefaultValueTextNoFallback text if key not found in any locale
ArgsTextNoComma-separated values for interpolation

Expression Formula

Translate("dashboard.title", "Dashboard", "")

No ghost dependency needed. Because Translate is an Expression (Function), OutSystems re-evaluates it on every render, and the library triggers a render when translations load or the locale changes. Do not pass CurrentLocale or TranslationRevision as trailing parameters — that pattern is obsolete. Called before init → returns DefaultValue, never crashes.

Args — Interpolation

JSON:

{ "welcome": "Hello, %{opt1}. You have %{opt2} notifications." }

Expression:

Translate("common.welcome", "Hello", "Gokula,5")

Output: "Hello, Gokula. You have 5 notifications."

Args Valueopt1opt2opt3
"Gokula"Gokula
"Gokula,5"Gokula5
"Gokula,5,New"Gokula5New

Resolution Priority

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

9. Automatic Re-render (How Reactivity Works)

The library reuses the OutSystems locale publish/subscribe pipeline — the same mechanism the platform uses to re-render on a locale change. You do not manage a client variable.

When a lazy translation file finishes loading, the library publishes a re-render for the active locale. Every Translate expression re-evaluates and picks up the newly loaded text. The screen shows DefaultValue first, then swaps to the translation with no flicker — all non-blocking.

When you call SwitchLocale, the library sets the active locale, syncs the OutSystems locale (so native date/number formatting follows), and publishes a re-render. Again, no manual assignment.

Old pattern (removed)Now
Create a TranslationRevision client variableNothing — not needed
Translate(Key, Default, Args, CurrentLocale, TranslationRevision)Translate(Key, Default, Args)
Increment revision after each load / switchAutomatic via publish/subscribe

Requirement: the value must be a Translate Expression (Function = Yes). Static text set directly on a widget will not update.


10. SwitchLocale & RTL

Where: Language switcher button/dropdown OnClick

Input Parameters

ParameterTypeMandatoryDescription
LocaleText✅ YesTarget locale code (e.g., "ar-SA")

Switcher Flow (Button OnClick)

1. SwitchLocale(Locale: "ar-SA")         ← sets locale, syncs OS, applies dir/lang, re-renders
2. LoadScreenResource(ScreenName: ...)   ← only if the target screen has modules not yet loaded

There is no Assign Client.CurrentLocale step and no revision increment. SwitchLocale handles direction and re-render for you.

RTL Support

Built-in RTL locales: ar-SA, ar-EG, he-IL, ur-PK, fa-IR (extend via SetConfig). The library takes ownership of the OutSystems RTL flag, so direction holds correctly across navigation — including for locales OutSystems does not ship natively (ur-PK, he-IL, fa-IR), which previously stayed left-to-right.

/* Use CSS logical properties or [dir="rtl"] selectors */
.content { margin-inline-start: 16px; padding-inline-end: 8px; }
[dir="rtl"] .sidebar { right: 0; left: auto; }

11. TranslateValidationMessages

Where: After SwitchLocale. Translates the OutSystems built-in validation messages (“Required field”, “Invalid email”, …).

TranslateValidationMessages(EspaceName: "MyApp")

The original (default-locale) messages are captured on first call and used as fallbacks, so switching back restores them correctly.


12. ObserveDOM (Optional)

Where: Screen OnReady, for text placed directly in HTML (outside Translate expressions).

<span data-trans="dashboard.title" data-trans-default="Dashboard"></span>
<span data-trans="common.greeting" data-trans-default="Hello" data-trans-args="Gokula,5"></span>
AttributePurpose
data-transTranslation key
data-trans-defaultFallback text
data-trans-argsComma-separated interpolation values
Screen OnReady:    ObserveDOM()             ← translates existing nodes, watches new ones
Screen OnDestroy:  DisconnectObserver()     ← no argument — tears down the internal observer

The observer is tracked inside the library, so you do not store its handle — DisconnectObserver() with no argument tears down whatever ObserveDOM() last created. Calling ObserveDOM() again auto-disconnects the previous one. These nodes also re-translate automatically when a lazy fetch lands.


13. SetConfig / GetConfig

SetConfig overrides configuration at runtime (empty value = keep current). TranslationMap and RtlLocales merge into the existing values; all other keys replace.

ParameterTypeDescription
BasePathTextOverride base URL
ResourceVersionTextOverride cache-bust string
LocalesTextComma-separated locale list
DefaultLocaleTextOverride default fallback locale
CacheStrategyText"force-cache", "no-cache", "reload", "default"
EnableFallbackBooleanTrue / False
SetConfig(
    BasePath:        "",          ← unchanged
    ResourceVersion: "v2.0",     ← updated
    CacheStrategy:   "no-cache", ← updated
    EnableFallback:  True         ← unchanged
)

GetConfig returns a snapshot of the current values (BasePath, ResourceVersion, Locales, DefaultLocale, CacheStrategy, EnableFallback) for debugging.


14. Common Patterns

Pattern 1 — Simple Text

Translate("common.save", "Save", "")

Pattern 2 — Text With Variables

// JSON: "Last updated: %{opt1}"
Translate("dashboard.lastUpdated", "Last updated", FormatDateTime(Now(), "dd MMM yyyy"))

Pattern 3 — Multiple Args

// JSON: "%{opt1} — %{opt2} on %{opt3}"
Translate("dashboard.list.item", "", "John,Transfer,15 Jul")

Pattern 4 — Conditional Default

Translate("premium.feature", If(IsPremiumUser, "Unlock", "Upgrade to access"), "")

Pattern 5 — Inside a List/Table

// In a Table Records expression:
Translate("status." + GetRecordList.Current.Status, GetRecordList.Current.Status, "")

JSON structure:

{
    "status": {
        "active": "نشط",
        "inactive": "غير نشط",
        "pending": "قيد الانتظار"
    }
}

15. JSON File Format

Rule: Use ONE Format Per File

✅ Pure Flat✅ Pure Nested❌ Mixed (Error)
All values are stringsObjects contain objectsNested object with dot-in-key

✅ Pure Flat Example

{
    "dashboard.title": "Dashboard",
    "dashboard.subtitle": "Your overview",
    "dashboard.summary.total": "Total"
}

✅ Pure Nested Example

{
    "dashboard": {
        "title": "Dashboard",
        "subtitle": "Your overview",
        "summary": { "total": "Total" }
    }
}

❌ This Will Throw Error

{
    "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.


16. Troubleshooting

Expressions show DefaultValue, never the translation

  1. Confirm the value is a Translate Expression (Function = Yes), not static widget text.
  2. Confirm the screen name is mapped in TranslationMap (or that the key lives in default/).
  3. Confirm the JSON file exists at the expected URL and the key spelling matches.

Locale switched but text didn't change

  1. Confirm you called SwitchLocale (not just changed a variable).
  2. If the target screen has its own module, call LoadScreenResource after the switch.
  3. Confirm the JSON file exists for the new locale (otherwise the fallback text shows).

RTL not flipping

  1. Confirm SwitchLocale ran (it sets dir / lang on <html>).
  2. Confirm the locale is in rtlLocales (default: ar-SA, ar-EG, he-IL, ur-PK, fa-IR).
  3. Use CSS logical properties or [dir="rtl"] selectors in your theme.

Console: 404 for some locale files

This is normal. If you have 5 locales configured but only 2 JSON files for a screen, the missing ones 404 silently and the fallback locale is used instead.


Console warns “LocaleService unavailable” (once)

Cause: the OutSystems locale service could not be resolved. Translations still load and render; the automatic re-render is disabled.
Fix: ensure InitTranslation runs in Application OnReady.


Translation shows [missing "en-US.some.key" translation]

Cause: the key doesn't exist in any loaded JSON file.
Fix: check the key spelling, confirm the right module JSON is loaded for the screen, and add a DefaultValue to the Translate call as a safety net.


I18n_Lib — wrapper v1.5.1 · OutSystems 11+ Reactive Web. See CHANGELOG for version history.


1.1.0

1. Overview

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

2. Prerequisites

ItemDetails
Module dependencyReference I18n_Lib in your application
JSON filesHosted under a /locales/ folder (app resources or CDN)

3. File Structure

/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.


4. Client Actions Reference

Client ActionWhere to CallPurpose
InitTranslationApplication OnReadyInitialize library, load default translations
LoadScreenResourceScreen OnReadyLoad screen-specific translations
TranslateExpressions (Function)Get translated text by key
SwitchLocaleLocale switcher buttonChange active language
SetConfigAnywhere (optional)Override config at runtime
GetConfigDebug/testingRead current config values

5. InitTranslation

Where: Application → OnReady (runs once on app load)

Input Parameters

ParameterTypeMandatoryDescription
BasePathText✅ YesURL to the /locales/ folder. Relative or absolute (CDN).
TranslationMapTranslationMapItem ListNoMaps screen names → JSON folder names
ResourceVersionTextNoCache-bust string (e.g., "v1.3"). Appended as ?t=v1.3
LocalesText ListNoLocale codes to preload. Empty = ["en-US"]

TranslationMapItem Structure

TranslationMapItem (Record)
├── Key   : Text    → OutSystems screen name (exact match)
└── Value : Text    → JSON folder name under basePath

How to Build

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
)

What Happens

  1. Library initializes with config
  2. Fetches default/en-US.json and default/ar-SA.json
  3. Stores translations in memory
  4. Library is ready for loadForScreen() and t() calls

Timing Note

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.


6. LoadScreenResource

Where: Each Screen → OnReady event

Input Parameters

ParameterTypeMandatoryDescription
ScreenNameText✅ YesThe OutSystems screen name (must match a TranslationMap key)

Output Parameters

ParameterTypeDescription
IsLoadedBooleanTrue if translations loaded (including fallback). False only if default locale also failed.

How to Use

// Screen OnReady:
LoadScreenResource(ScreenName: "Dashboard")

// Then assign:
TranslationRevision = TranslationRevision + 1

Behaviour

ScenarioIsLoadedWhat Renders
en-US.json + ar-SA.json both foundTrueTranslated text
Only en-US.json found (ar-SA 404)Trueen-US text (fallback)
All files 404FalseDefaultValue from Translate
Screen not in TranslationMapTrueOnly default/ keys available

When Screen Name Is Not Mapped

If ScreenName doesn't exist in TranslationMap, no screen-specific JSON is loaded — but default/ translations are still available. This is not an error.


7. Translate (Expression)

Where: Any Expression widget — set Function = Yes

Input Parameters

ParameterTypeMandatoryDescription
KeyText✅ YesTranslation key (e.g., "dashboard.title")
DefaultValueTextNoFallback text if key not found in any locale
ArgsTextNoComma-separated values for interpolation

Output

ParameterTypeDescription
TranslatedTextTextThe resolved translation

Expression Formula

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.

Args — Interpolation

JSON:

{ "welcome": "Hello, %{opt1}. You have %{opt2} notifications." }

Expression:

Translate("common.welcome", "Hello", "Gokula,5")

Output: "Hello, Gokula. You have 5 notifications."

Args Valueopt1opt2opt3
"Gokula"Gokula
"Gokula,5"Gokula5
"Gokula,5,New"Gokula5New
""(no interpolation)

Resolution Priority

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

8. SwitchLocale

Where: Language switcher button/dropdown OnClick

Input Parameters

ParameterTypeMandatoryDescription
LocaleText✅ YesTarget locale code (e.g., "ar-SA")

Full Switcher Flow (Button OnClick)

1. SwitchLocale(Locale: "ar-SA")
2. Assign → Client.CurrentLocale = "ar-SA"
3. LoadScreenResource(ScreenName: GetCurrentScreen())

9. SetConfig

Where: Anywhere at runtime (optional — for advanced use)

Input Parameters

ParameterTypeMandatoryDescription
BasePathTextNoOverride base URL
ResourceVersionTextNoOverride cache-bust string
LocalesTextNoComma-separated locale list
DefaultLocaleTextNoOverride default fallback locale
CacheStrategyTextNo"force-cache", "no-cache", "default"
EnableFallbackBooleanNoTrue/False

Rule: Empty String = Keep Current Value

SetConfig(
    BasePath:        "",          ← unchanged
    ResourceVersion: "v2.0",     ← updated
    Locales:         "",          ← unchanged
    DefaultLocale:   "",          ← unchanged
    CacheStrategy:   "no-cache", ← updated
    EnableFallback:  True         ← unchanged
)

When to Use

  • Switch CDN endpoint mid-session
  • Force cache refresh after deployment
  • Enable/disable fallback for testing

10. GetConfig

Where: Debug screens, developer tools

Output Parameters

ParameterTypeDescription
BasePathTextCurrent base path
ResourceVersionTextCurrent version string
LocalesTextComma-separated active locales
DefaultLocaleTextCurrent default locale
CacheStrategyTextCurrent cache strategy
EnableFallbackBooleanWhether fallback is active

Usage

GetConfig()
// Use outputs for debug display or conditional logic

11. Common Patterns

Pattern 1 — Simple Text

Translate("common.save", "Save", "")

Pattern 2 — Text With Variables

// JSON: "Last updated: %{opt1}"
Translate("dashboard.lastUpdated", "Last updated", FormatDateTime(Now(), "dd MMM yyyy"))

Pattern 3 — Multiple Args

// JSON: "%{opt1} — %{opt2} on %{opt3}"
Translate("dashboard.list.item", "", "John,Transfer,15 Jul")

Pattern 4 — Conditional Default

Translate("premium.feature", If(IsPremiumUser, "Unlock", "Upgrade to access"), "")

Pattern 5 — Inside a List/Table

// In a Table Records expression:
Translate("status." + GetRecordList.Current.Status, GetRecordList.Current.Status, "")

JSON structure:

{
    "status": {
        "active": "نشط",
        "inactive": "غير نشط",
        "pending": "قيد الانتظار"
    }
}

Pattern 6 — Dynamic Screen Name

// Reusable across screens:
LoadScreenResource(ScreenName: GetCurrentScreenName())

12. JSON File Format

Rule: Use ONE Format Per File

✅ Pure Flat✅ Pure Nested❌ Mixed (Error)
All values are stringsObjects contain objectsNested object with dot-in-key

✅ Pure Flat Example

{
    "dashboard.title": "Dashboard",
    "dashboard.subtitle": "Your overview",
    "dashboard.summary.total": "Total",
    "dashboard.alerts.empty": "No active alerts."
}

✅ Pure Nested Example

{
    "dashboard": {
        "title": "Dashboard",
        "subtitle": "Your overview",
        "summary": {
            "total": "Total"
        },
        "alerts": {
            "empty": "No active alerts."
        }
    }
}

❌ This Will Throw Error

{
    "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.


13. Troubleshooting

Error: "Invalid or missing I18n instance"

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).


Expressions show DefaultValue, not translation

Check:

  1. Is CurrentLocale included in the Translate expression? (ghost dependency)
  2. Is TranslationRevision included in the Translate expression? (ghost dependency)
  3. Is TranslationRevision incremented after LoadScreenResource?
  4. Is the screen name in TranslationMap?
  5. Does the JSON file exist at the expected URL?

Locale switched but text didn't change

Check:

  1. Did you call LoadScreenResource after SwitchLocale?
  2. Did you increment TranslationRevision?
  3. Does the JSON file exist for the new locale?

RTL not flipping

Check:

  1. Did you set dir attribute on <html> after switch?
  2. Is the locale in the rtlLocales list? (default: ar-SA, ar-EG, he-IL, ur-PK, fa-IR)
  3. Are you using logical CSS properties or [dir="rtl"] selectors?

Console: 404 for some locale files

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.


Translation shows [missing "en-US.some.key" translation]

Cause: Key doesn't exist in any loaded JSON file.
Fix:

  1. Check spelling of key in JSON file
  2. Check that the correct module JSON is loaded for this screen
  3. Add a DefaultValue to the Translate call as safety net