Localization (i18n)
Set up multi-language apps with app.i18n, BF.i18n(), schema *_calc fields, and a language picker. Includes IDE placement, persistence, and limits of the runtime helper.
BetterForms i18n is a dictionary on the App Model plus a lookup helper. There is no separate translation service, plural engine, or ICU formatter.
BF.i18n(key) (in BF.js) reads app.i18n.dict[key][langSelected], then app.i18n.dict[key][langDefault]. An empty or missing key returns ''. If both languages are missing, the helper returns No default lang value or invalid key.
Use this page to add languages to an app, wire UI strings, and persist the user’s choice.
What this is not
Dictionary lookup by key + language id
Yes
Fallback to langDefault
Yes
Interpolation (Hello, {name}), plurals, nested JSON paths
No. The key is a single dict property name, even if it contains dots.
BF_i18n
No. Some internal notes mention it; BF.js only defines BF.i18n.
Automatic <html lang>
No. That is SEO language / language_calc on the page. See SEO Meta Tags.
Caching app.i18n.langSelected directly
No. App Model Caching watches top-level keys only (app[path]). Use a sibling key such as currentLang.
Where it lives in the IDE
Open Site settings → Environment → App Model (JSON).
Look for a top-level
i18nobject. If it is missing, add the example below (do not delete other App Model keys).Global scripts such as
onAppLoadandselectLanguagego in site-level named actions (site.content.namedActions). See Named Actions.
BF.i18n() always reads store.state.site.content.app. If app.i18n is missing, the browser helper throws when it reads langSelected. SSR / safe-calc stubs return '' instead. Create the object before calling the helper in page HTML.
App Model shape
dict
Map of string keys → { "<langId>": "text" }.
languages
Picker list. id must match keys inside each dict entry.
langSelected
Language used first. Change this to switch UI copy.
langDefault
Fallback when the selected language has no value for a key.
currentLang
Recommended top-level cache key. Not part of i18n.
languages is only for your picker UI. BF.i18n() does not read it.
Add i18n to pages (practical workflow)
Work one page at a time. Keep existing App Model data; only add keys.
Confirm
app.i18nexists (previous section).Decide which languages you need (
en,de, …). Add them tolanguagesand to eachdictentry.Open a page. In HTML and in the page JSON schema, list every user-visible string you want translated (headings, buttons,
label, help text, empty states).Replace those strings with
BF.i18n('yourKey')(next section).Add the same keys under
app.i18n.dictwith a value per language. Prefer a value for every listed language pluslangDefault.Repeat for each page, then for slots and navigation HTML that shows copy.
Add a language picker (below) and persist
currentLang.
Schema calculations: any *_calc field is compiled as an expression (bfUtils.js). For field labels use:
Static "label": "First name" does not go through BF.i18n unless you change it to label_calc.
Use BF.i18n() in HTML
The HTML renderer exposes BF on the template, so BF.i18n('heading1') is the call that matches BF.js.
Changing app.i18n.langSelected re-runs lookups on the next Vue render. Templates that call BF.i18n() during render typically update immediately. A string you copied into a plain variable will not.
Restore language on app load
App Model Caching (site.content.appCaching) watches app[path] — a top-level property. Cache currentLang, not i18n.langSelected.
In Site settings → App Model, enable caching for currentLang (localStorage to keep the choice across visits). See App Model.
In the global onAppLoad named action:
onAppLoad runs after the site loads in the browser. It can be skipped with ?_onAppLoad=0.
Language picker
Global named action selectLanguage (function action):
HTML (site named actions are callable from any page):
Or a select (still write the cache key so App Model Caching persists it):
Optional: keep <html lang> in sync with the same code via page language_calc (SEO), for example "app.i18n.langSelected || 'en'". That attribute is independent of BF.i18n().
Troubleshooting
Red error / Cannot read property 'langSelected'
app.i18n is missing in the App Model.
No default lang value or invalid key
Key missing, or no string for langSelected and langDefault.
SSR HTML blank, browser shows the fallback string
Empty key, or SSR stub returned '' because app.i18n.dict was missing at render.
Picker does nothing
selectLanguage is not a site named action, or options.lang does not match a dict language id.
Choice lost on refresh
currentLang is not in App Model Caching, or you cached a nested path (not supported).
Label still English
Field still uses static label instead of label_calc.
See also
Klai Utility Functions —
BF.i18n(key)rowApp Model — caching and
app.*Named Actions —
onAppLoad,namedActionSEO Meta Tags —
<html lang>
Last updated
Was this helpful?