Skip to content

Internationalisation

theGreenGuy edited this page Jun 14, 2026 · 9 revisions

Internationalisation (i18n)

The operator/management SPA supports English, German, French, Spanish, and Chinese. English is the source language and the inline fallback; backend output, server logs, and the login screen stay English regardless of the user's choice.

A user's preferred language is stored on their IAM account (AppUser.language, default en) so it follows them across devices. The browser caches it in localStorage (owcs.lang) for an instant first paint before the server round-trip completes.

Switching languages

A language picker lives in the top bar next to the warehouse switcher. Each option is labelled in its own script (Deutsch, Français, Español, 中文) so a user who cannot read the current language can still find theirs. Selecting a language:

  1. Updates the <html lang> attribute immediately.
  2. Writes the choice to localStorage (cache for next load).
  3. Persists it to the IAM account via PUT /api/iam/me/language (best-effort; a network failure still changes the language for the current session).

IAM language API

Both endpoints are resolved from the X-Auth-User header forwarded by the gateway.

Method Path Description
GET /api/iam/me/language Returns { "language": "en" } for the signed-in user. Falls back to en when the user has no row.
PUT /api/iam/me/language Body { "language": "de" }. Unknown codes are coerced to en. Auto-provisions the AppUser row if the user has never been managed in IAM, so the preference still sticks.

Supported codes: en · de · fr · es · zh.

Framework internals (src/i18n/)

The i18n layer is dependency-free (no i18n library):

File Role
config.ts Lang union type, LANGS array, DEFAULT_LANG = 'en', LANG_STORAGE_KEY.
LanguageContext.tsx React context + LanguageProvider. Reads the server preference once on mount; exposes lang + setLang.
useT.ts useT(ns) hook — returns t(key, english). When lang === 'en' or the key is absent, the inline English is returned verbatim (no raw key ever leaks).
dictionaries.ts Auto-discovers every locales/<lang>/<namespace>.ts via import.meta.glob. No central registry — parallel namespace work is conflict-free.
LanguageSwitcher.tsx Top-bar <Select> wired to LanguageContext.
locales/<lang>/<namespace>.ts One default-export { key: 'translated text' } object per language × namespace. English needs no files.

Translated namespaces

The table below covers all 24 namespaces (≈ 1,700 keys) shipped in de, fr, es, and zh. The whole SPA is now fully translated — every screen in the sidebar is covered; no screen falls back to English-only rendering.

Namespace Screen / area Introduced
nav Sidebar labels and section headers (AppShell.tsx) #289
users Users admin screen #290
access Access Control admin screen #290
warehouseaccess Warehouse Access admin screen #290
admindb Database Console admin screen #290
systeminfo System Info admin screen #290
dashboard Dashboard home screen (Dashboard.tsx) #291
help In-app help drawer chrome + per-screen content (HelpButton.tsx) #291
process BPMN process designer (ProcessDesigner.tsx) #291
reporting Reporting screens — Material flow, ASRS, Stock, Inbound, Outbound + shared charts #291
inbound Inbound orders screen (InboundScreen.tsx) #292
outbound Outbound orders screen (OutboundScreen.tsx) #292
counting Stock counting / cycle-count screen (CountingScreen.tsx) #292
stocktxn Stock transactions screen (StockTxnScreen.tsx) #292
inventory Stock overview + Handling units screens (StockOverviewScreen.tsx, HandlingUnitsScreen.tsx) #292
gtpops GTP operator console — launcher, queue/exceptions drawers, count/pick/put panels (GtpOpsScreen.tsx) #292
gtpconfig GTP configuration — workplace/node CRUD dialogs, location picker (GtpConfigScreen.tsx, LocationPicker.tsx) #292
transport Transport overview — tiles, filters, task table, trace dialog (TransportScreen.tsx) #292
twin Hardware twin — page chrome, stat chips, legend, level select, detail panels (HardwareTwinScreen.tsx) #292
masterdata Master data catalogs — warehouses, SKUs, storage blocks, locations, equipment, HU types, label templates + guided block builder (MasterDataScreen.tsx); 297 keys #293
settings Settings — slotting policy, cubing, counting, stock rules, integrations, system status, demo mode, hardware emulator (SettingsScreen.tsx); 196 keys #293
slotting Slotting — pick faces + block slotting screen (SlottingScreen.tsx); 40 keys #293
topology Automation topology editor — TopologyEditor, AutomationTopology3D (UI only; 3D geometry/mesh/colour untouched), RoutingGraphTables, RouteTest, PlanEditor2D: tabs, toolbar, level controls, equipment library, properties panel, function-point and connection dialogs, node-link panel, routing-graph tables, 2D plan editor, all status/hint text; 273 keys #294

The nav namespace — sidebar navigation

AppShell.tsx uses useT('nav') to translate sidebar items. The keys are the screen's stable key property (e.g. inbound, gtp-ops). Section header keys use the prefix section: followed by the English section name (e.g. section:Master data).

All 32 screen labels and 6 section headers are covered in locales/<lang>/nav.ts for de, fr, es, and zh. When you add a new screen to SCREENS, also add its key to each nav.ts file; until you do, the English label is shown as a fallback.

// src/i18n/locales/de/nav.ts (excerpt)
export default {
  inbound: 'Wareneingangsaufträge',
  'section:Master data': 'Stammdaten',
  // ...
}

The help namespace — in-app help drawer

The help namespace is shared across all screens and uses compound keys rather than the single-level keys used by other namespaces.

Drawer chrome keys (always translated):

Key English default
help Help
helpFor Help for {screen}
closeHelp Close help
tips Tips

Per-screen content keys (the English source stays in help/content.ts; HelpButton.tsx calls t(key, english) so untranslated screens degrade to English automatically):

Key pattern Example
{screenKey}.summary dashboard.summary
{screenKey}.s{i}.heading dashboard.s0.heading
{screenKey}.s{i}.body dashboard.s0.body
{screenKey}.tip{i} dashboard.tip0

Section headings (s{i}.heading) are translated for all 33 screens. Full body copy is translated for the priority screens (dashboard, processes, and all five reporting entries). The remaining screens' summaries, bodies, and tips fall back to English automatically.

// src/i18n/locales/de/help.ts (excerpt)
export default {
  "help": "Hilfe",
  "closeHelp": "Hilfe schließen",
  "dashboard.s0.heading": "Was Sie hier tun",
  "dashboard.summary": "...",  // priority screen — body copy translated
  "transport.s0.heading": "Was Sie hier tun",
  // transport summary/body omitted → falls back to English
}

Adding translations to a screen

  1. Import and call useT with a namespace (one per screen / feature area):

    import { useT } from '../i18n/useT'
    
    const t = useT('inbound')
    return <h1>{t('title', 'Inbound orders')}</h1>

    The second argument is the English source text and the fallback — wrapping a string with t(...) never changes the English UI. Missing translations degrade to English, not a raw key.

  2. Add locale files for each of de, fr, es, zh:

    // src/i18n/locales/de/inbound.ts
    export default {
      title: 'Wareneingangsaufträge',
    }

    dictionaries.ts picks up the file automatically — no registry edit needed.

Do not wrap: data values, codes (SKU/HU/location codes), log text, or anything the backend owns. Keep numbers, units, and identifiers out of translated strings; interpolate them in the component.

What stays English

  • Backend output and API responses.
  • Server logs (all Java / Go services).
  • The Keycloak login screen (rendered outside the LanguageProvider) — including its self-service change-password form.
  • Error codes and identifiers shown in the UI (HU codes, location codes, SKU codes, etc.).
  • Service health status codes (UP / DOWN / UNKNOWN) — these are API data values, not UI copy.
  • The screen-permission catalog labels in the Access Control matrix — they mirror the nav catalog and are intentionally kept in sync with the English screen keys rather than translated separately.

Clone this wiki locally