One program in several languages: copy from per-locale catalogs, records seeded per locale, right-to-left layout.
A program declares the languages it ships in the app header. Copy moves into one catalog per language, and the viewer's locale picks which one renders.
app greeting "1.0.0"
"A greeting in three languages."
locales [en, ar, sl]en is required. For each locale, a flat JSON catalog sits beside the program at locales/<locale>/<app>.json:
▼{
"hello": "Hello from Orb",
"cities": "{{n}} cities, seeded in your language"
}▼(render-ui main { type: stack, direction: vertical, gap: sm, children: [
{ type: typography, content: (i18n/t "hello"), variant: h3 },
{ type: typography, content: (i18n/t "cities" { n: (array/len ?data) }), variant: small }
] })(i18n/t "key") and @locale are render-time: they belong inside render-ui, never in a guard, a set or an emit. orb validate checks the catalogs against the calls: a key missing from any declared locale, placeholders that do not match the parameters, and keys nothing uses are all reported. Keys are qualified with the app's name when the program lowers, so catalogs from many imported behaviors merge without collisions.
A browser-stored entity can seed different rows for each language:
Greeting
on City · states: 2 · transitions: 41app std-orb-docex-greeting "1.0.0"
2"std-orb-docex-greeting — one program in three languages: copy from per-locale catalogs, records seeded per locale."
3locales [en, ar, sl]
4
5orbital GreetingOrbital {
6 entity City [persistent: docex_cities, local] {
7 id : string!
8 name : string!
9 instances [
10 { id: "C-1", name: "Ljubljana" },
11 { id: "C-2", name: "Riyadh" }
12 ]
13 instances ar [
14 { id: "C-1", name: "ليوبليانا" },
15 { id: "C-2", name: "الرياض" }
16 ]
17 instances sl [
18 { id: "C-1", name: "Ljubljana" },
19 { id: "C-2", name: "Rijad" }
20 ]
21 }
22
23 trait Greeting -> City [interaction, collection] {
24 initial: loading
25
26 state loading {
27 INIT -> loading
28 (fetch City { emit: { success: CitiesLoaded, failure: CitiesFailed } })
29 CitiesLoaded -> ready
30 (emit MOVED { event: "CitiesLoaded" })
31 (render-ui main { type: stack, direction: vertical, gap: sm, children: [
32 { type: typography, content: (i18n/t "hello"), variant: h3 },
33 { type: typography, content: (i18n/t "cities" { n: (array/len ?data) }), variant: small, color: muted },
34 { type: data-list, entity: ?data, fields: [{ name: "name", label: (i18n/t "city"), variant: h4 }] }
35 ] })
36 CitiesFailed -> ready
37 (emit MOVED { event: "CitiesFailed" })
38 (render-ui main { type: alert, variant: error, message: ?error })
39 }
40
41 state ready {
42 INIT -> loading
43 (emit MOVED { event: "INIT" })
44 (fetch City { emit: { success: CitiesLoaded, failure: CitiesFailed } })
45 }
46
47 emits {
48 CitiesLoaded {
49 data : [City]
50 }
51 CitiesFailed {
52 error : string
53 }
54 MOVED -> external {
55 event : string!
56 }
57 @description "Each move names the event it took, so the diagram beside the demo can follow it."
58 }
59 }
60
61 page "/greeting" -> Greeting
62}instances holds the default locale's rows; once one instances <locale> block is written, every other declared locale needs one. Each locale gets its own browser database, so a visitor's records stay in one language.
A site usually serves each language under a prefix: /, /ar, /sl. A page renders under the locale its path names, and right-to-left locales such as Arabic set dir="rtl" for the whole page. Long-form content, such as an article or these docs, is written once per language as its own behavior rather than as catalog keys.
The language's own keywords, tags and operators also exist in Arabic and Slovenian, so a program can be written natively. Your own names (entities, traits, states, events) never translate, so a native program and its English twin lower to the same .orb. orb i18n vocab prints the vocabulary.
The tutorials start with a complete orbital.
The language where the rule is the program.