i18n locale packs
Multi-language SaaS — translate ~20 keys, ship a French build in 20 minutes.
The shipped builder is English-only out of the box. Multi-language SaaS builds layer locale packs on top — French / Vietnamese / Spanish / whichever audience you ship to. This doc shows the i18n API + the pattern for shipping a locale. Time budget: 20 minutes.
The contract
Every UI string in the engine routes through I18n.t('key'). The base dictionary is src/lang/en.json. To translate, you call I18n.init({...}) with a flat key/value object before builder.load() runs.
Two key namespaces
There are two top-level namespaces (W6.C.14 lesson):
- *`widgets.
** — palette item labels (widgets.heading,widgets.paragraph,widgets.image`, …). - *`elements.
** — canvas + settings panel labels (elements.heading,elements.paragraph,elements.image`, …). - *`controls.
** — control labels (controls.text_color,controls.font_size`, …).
A locale pack must translate all three namespaces it cares about — translating only widgets.* and you get a half-translated UI (palette in French, panel still in English).
Step 1 — Pre-flight
Step 2 — Author your locale pack
Start from src/lang/en.json (the canonical key list). Translate the keys you need:
- Match key spelling exactly to
en.json— a typo means the engine falls back to the key itself ("widgets.heading" appearing in the UI). - Translate every key in the namespaces you ship — half-translation produces a half-translated UI.
- Keep the dict flat —
'widgets.heading': 'Titre', notwidgets: { heading: 'Titre' }. The engine looks up by literal dot-key.
Step 3 — Wire the dictionary
Call I18n.init() BEFORE you call builder.load(). Otherwise the first paint uses English defaults, then flips to your locale on the next mutation:
Step 4 — Locale switcher (optional)
To let buyers flip locale at runtime, swap the dict + repaint:
Common patterns
Locale from URL
For SaaS hosts where the active locale lives in the URL (/fr/builder, ?locale=fr), pick the dict server-side and inline it:
Custom widget keys
When you author a custom Widget (per Build a custom Widget), pick a key namespace under widgets.<your-namespace>.* so your translations don't collide with stock widget keys:
Auditing for missing keys
If a label still renders in English after I18n.init(), the key is missing from your dict. The engine falls back to the literal key string when no value is found — useful as a debug heuristic but ugly in production. Run a CI check that walks src/lang/en.json and asserts every key exists in each shipped locale pack:
Numbers, dates, currencies (pluralisation + formatting)
I18n.t() covers UI string lookup only. For number / date / currency formatting use the browser's Intl API — it's per-locale-correct without requiring any translation effort:
RTL languages (Arabic, Hebrew)
Set dir="rtl" on the canvas's <html> or container, and the shipped Page templates flip automatically (the engine uses logical CSS properties under the hood). Per-element overrides via formats.text_align: "right" work for cases where a particular element should buck the document direction.
Did you make it?
You should now be able to:
- ✅ State the three namespaces (
widgets.*,elements.*,controls.*) and what each one covers. - ✅ Author a flat dict with translated keys matching
src/lang/en.json. - ✅ Call
I18n.init(dict)beforebuilder.load()to set the active locale. - ✅ Switch locales at runtime by re-calling
init()+load()+ re-rendering the palette.
If a step didn't work:
- 🩹 Some labels still in English → that namespace isn't translated. Check
controls.*if panel labels are English; checkwidgets.*if palette labels are. - 🩹 Switching locale leaves orphan widgets in palette → clear
widgetsContainer.innerHTML = ''beforewidgetsBox.render()(W6.C.14 lesson — engine doesn't auto-clear). - 🩹 Custom widget label not translated → your custom widget's
getName()must returnI18n.t('widgets.<key>'), not a literal string.
What's next?
- → FAQ — common questions on version, email-client matrix, debug recipes.
- 🔗
examples/extensions/4-i18n-locale-pack/— runnable EN / FR / VI switcher. - 🔗
docs/core/LANGUAGE.md— the canonical i18n spec. - 🔗
src/lang/en.json— the canonical key list to translate from.