Table of Contents
Shipping an educational lab to a global audience means more than translation strings. Bio-Dynamics adds RTL layout for Persian, keyboard region shortcuts, ARIA live announcements, touch gesture hints, and URL-encoded lab checkpoints so teachers can share a mid-simulation state without a backend.
Lightweight i18n without a framework
Locales live in src/i18n/en.ts, de.ts, and fa.ts. A small t() helper resolves dot-path keys with parameter interpolation:
| |
Locale selection order:
?lang=en|de|faquery paramnavigator.languageheuristic (de,fa/perprefixes)- Default
en
applyDocumentLocale() sets document.documentElement.lang and dir="rtl" for Persian — the dashboard CSS uses logical properties where needed so RTL does not require a duplicate stylesheet.
Event log lines from the engine are English internally; translateEvent() maps known prefixes to localized strings on display.
User guide (sharing and URL params): docs/user-guide.md
Accessibility choices that survived scope pressure
| Feature | Implementation |
|---|---|
| Keyboard region select | Keys 1–7 map to region list order; Esc returns to macro body |
| Focusable actions | Catalog and trigger buttons carry aria-labels |
| Live announcements | aria-live="polite" region updates on region change, preset switch, and major sim events |
| Touch hints | Dismissible gesture card on coarse pointers; hidden on fine-pointer desktops |
| Reduced motion respect | Camera fly-to uses short easing; no gratuitous particle bursts |
These are not a full WCAG audit claim — the lab is a canvas-heavy WebGL demo — but they keep the control surface operable without a mouse.
Shareable lab state without a server
labState.ts serializes a mid-simulation checkpoint to:
- URL —
?lab=plus a compact base64url payload (preset, region, tick, biome scalars, nodes, recent events) localStorage— autosave on meaningful progress for resume-after-refresh
Copy lab link in the dashboard encodes the current snapshot. Opening that URL restores the exact node layout and meter values — useful for classroom demos (“start here after the allergen spike”).
Resume banner logic:
- Offer restore when autosaved state exists and is younger than 7 days
- Skip if URL already contains
?lab= - Dismiss sets
sessionStorageso “Start fresh” does not nag in the same session
Source: src/state/labState.ts
URL params as product surface
Beyond lab checkpoints, query params drive preset deep links from health articles:
| Param | Example | Effect |
|---|---|---|
preset | allergy, candida, lifecycle | Scenario framing and default env |
region | nose, gut, vaginal | Opens tissue context |
context | lifestage | Swaps allergy narrative to life-stage variant |
lang | de, fa | UI locale |
Health posts on omid.dev already embed companions with these URLs. The lab meets readers where the article left off.
Why client-only persistence is enough
Bio-Dynamics has no accounts, no database, no sync service. For an educational sandbox that ships on every commit to playground.omid.dev, that is a feature:
- Zero backend cost and ops
- Shareable URLs work forever as static links
- Privacy-friendly — no health data leaves the browser
The trade-off is payload size limits in URLs and no cross-device sync. Acceptable for 400 capped nodes with compact tuple encoding.
Read the rest of this series
- Designing a deterministic microbiome simulation
- Macro/micro 3D: one scene graph, seven tissue builders
- Catalog-driven dashboard: strains, stressors, and action impact
Full documentation index: docs/README.md
Omid Farhang