@selvajs/ui 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,10 @@
11
11
  import * as Resizable from '../primitives/resizable';
12
12
  import { Button } from '../..';
13
13
  import ParameterPresetManager from './ParameterPresetManager.svelte';
14
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
15
+
16
+ const locale = getLocaleContext();
17
+ const t = $derived(locale.messages);
14
18
 
15
19
  const COLLAPSED_WIDTH = 48;
16
20
 
@@ -74,7 +78,7 @@
74
78
  let isMobile = $state(false);
75
79
  let drawerOpen = $state(false);
76
80
 
77
- const activeLeftTabLabel = $derived(leftTabs[0]?.label ?? 'Parameters');
81
+ const activeLeftTabLabel = $derived(leftTabs[0]?.label ?? t.parametersTab);
78
82
 
79
83
  $effect(() => {
80
84
  const mqMobile = window.matchMedia('(max-width: 639px)');
@@ -200,7 +204,7 @@
200
204
  onclick={() => (drawerOpen = !drawerOpen)}
201
205
  ontouchstart={onDrawerTouchStart}
202
206
  ontouchend={onDrawerTouchEnd}
203
- aria-label={drawerOpen ? 'Collapse panel' : 'Expand panel'}
207
+ aria-label={drawerOpen ? t.collapsePanel : t.expandPanel}
204
208
  aria-expanded={drawerOpen}
205
209
  >
206
210
  <div class="drawer-pill"></div>
@@ -227,7 +231,7 @@
227
231
  <div class="my-4 gap-2 flex items-center">
228
232
  <div class="h-px flex-1 bg-border"></div>
229
233
  <span class="text-xs font-medium px-1 text-muted-foreground">
230
- {rightTabs[0]?.label ?? 'More'}
234
+ {rightTabs[0]?.label ?? t.moreTab}
231
235
  </span>
232
236
  <div class="h-px flex-1 bg-border"></div>
233
237
  </div>
@@ -257,7 +261,7 @@
257
261
  <div class="my-4 gap-2 flex items-center">
258
262
  <div class="h-px flex-1 bg-border"></div>
259
263
  <span class="text-xs font-medium px-1 text-muted-foreground">
260
- {rightTabs[0]?.label ?? 'More'}
264
+ {rightTabs[0]?.label ?? t.moreTab}
261
265
  </span>
262
266
  <div class="h-px flex-1 bg-border"></div>
263
267
  </div>
@@ -1,6 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { TabConfig } from '@selvajs/schemas';
3
3
  import Icon from '@iconify/svelte';
4
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
5
+
6
+ const locale = getLocaleContext();
7
+ const t = $derived(locale.messages);
4
8
 
5
9
  interface Props {
6
10
  side: 'left' | 'right';
@@ -29,7 +33,10 @@
29
33
  style="--collapsed-w: {collapsedWidth}px;"
30
34
  role="button"
31
35
  tabindex="0"
32
- aria-label="Expand {side} panel"
36
+ aria-label={t.expandSidePanel.replace(
37
+ '{side}',
38
+ side === 'left' ? t.panelSideLeft : t.panelSideRight
39
+ )}
33
40
  onclick={onExpand}
34
41
  onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && onExpand()}
35
42
  >
@@ -16,6 +16,8 @@
16
16
  import AppLayout from './AppLayout.svelte';
17
17
  import StateDisplay from '../primitives/StateDisplay.svelte';
18
18
  import { setClientSlot, type ClientSlot } from '../../contexts/clientSlotContext.svelte';
19
+ import type { Locale } from '../../i18n/messages';
20
+ import { setLocaleContext, getLocaleContext } from '../../i18n/localeContext.svelte';
19
21
 
20
22
  import type { Snippet } from 'svelte';
21
23
 
@@ -54,6 +56,13 @@
54
56
  externalScopeKey?: string;
55
57
  // Renders client-sourced inputs with presentation === 'slot'; receives { inputId, displayName, slotLabel, value }.
56
58
  clientSlot?: ClientSlot;
59
+ /**
60
+ * UI language for the app's own chrome (viewer, panels, status text).
61
+ * Provided once here and read by every descendant via locale context.
62
+ * Defaults to English when unset. Does not translate schema-authored labels
63
+ * or Grasshopper-sourced names/metadata.
64
+ */
65
+ lang?: Locale;
57
66
  }
58
67
 
59
68
  let {
@@ -81,13 +90,21 @@
81
90
  header,
82
91
  onReady,
83
92
  externalScopeKey,
84
- clientSlot
93
+ clientSlot,
94
+ lang
85
95
  }: Props = $props();
86
96
 
87
97
  // Make the host's client-input slot available to InputControl deep in the tree.
88
98
  // svelte-ignore state_referenced_locally
89
99
  setClientSlot(clientSlot);
90
100
 
101
+ // Provide the UI locale once for the whole app subtree (viewer, panels, status
102
+ // text). Resolution: explicit `lang` → any host-provided locale → English. The
103
+ // getter is re-read reactively, so switching `lang` updates the chrome live.
104
+ const hostLocale = getLocaleContext();
105
+ setLocaleContext(() => lang ?? hostLocale.locale);
106
+ const t = $derived(getLocaleContext().messages);
107
+
91
108
  const resolvedScopeKey = $derived(externalScopeKey || definitionKey || schema?.id || '');
92
109
 
93
110
  // Solve Session owns the value/lifecycle state machine; the request/response driver
@@ -182,7 +199,7 @@
182
199
  </div>
183
200
  {:else if !schema}
184
201
  <div class="min-h-100 flex items-center justify-center">
185
- <StateDisplay type="loading" size="large" message="Loading schema..." />
202
+ <StateDisplay type="loading" size="large" message={t.loadingSchema} />
186
203
  </div>
187
204
  {:else}
188
205
  {#key definitionKey}
@@ -3,6 +3,7 @@ import type { ActionButton } from '../../types/actionButton';
3
3
  import type { SolveFn } from '../../types/solveFn';
4
4
  import type { PresetLabels } from '../../types/presetLabels';
5
5
  import { type ClientSlot } from '../../contexts/clientSlotContext.svelte';
6
+ import type { Locale } from '../../i18n/messages';
6
7
  import type { Snippet } from 'svelte';
7
8
  interface Props {
8
9
  schema: UISchema;
@@ -38,6 +39,13 @@ interface Props {
38
39
  header?: Snippet;
39
40
  externalScopeKey?: string;
40
41
  clientSlot?: ClientSlot;
42
+ /**
43
+ * UI language for the app's own chrome (viewer, panels, status text).
44
+ * Provided once here and read by every descendant via locale context.
45
+ * Defaults to English when unset. Does not translate schema-authored labels
46
+ * or Grasshopper-sourced names/metadata.
47
+ */
48
+ lang?: Locale;
41
49
  }
42
50
  declare const ComputeApp: import("svelte").Component<Props, {}, "">;
43
51
  type ComputeApp = ReturnType<typeof ComputeApp>;
@@ -14,6 +14,10 @@
14
14
  import InputControl from './InputControl.svelte';
15
15
  import OutputDisplay from './OutputDisplay.svelte';
16
16
  import { evaluateGroupVisibility } from '../../schema/visibility-rules';
17
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
18
+
19
+ const locale = getLocaleContext();
20
+ const t = $derived(locale.messages);
17
21
 
18
22
  interface Props {
19
23
  tab: TabConfig;
@@ -79,7 +83,7 @@
79
83
  <ScrollArea class="h-full" orientation="vertical">
80
84
  <div class="p-4 tab-content-container">
81
85
  {#if tab.groups.length === 0}
82
- <StateDisplay type="empty" size="medium" message="This tab has no groups configured." />
86
+ <StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
83
87
  {:else}
84
88
  <div class="gap-8 flex flex-col">
85
89
  {#each tab.groups as group (group.id)}
@@ -1,6 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { Button } from './button';
3
3
  import { Zap, Check, Loader, Play } from '@lucide/svelte';
4
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
5
+
6
+ const locale = getLocaleContext();
7
+ const t = $derived(locale.messages);
4
8
 
5
9
  interface Props {
6
10
  hasPendingChanges: boolean;
@@ -25,16 +29,16 @@
25
29
  >
26
30
  {#if isSolving}
27
31
  <Loader class="h-4 w-4 animate-spin" />
28
- Solving...
32
+ {t.solvingEllipsis}
29
33
  {:else if hasNeverSolved}
30
34
  <Play class="h-4 w-4" />
31
- Press to calculate
35
+ {t.pressToCalculate}
32
36
  {:else if hasPendingChanges}
33
37
  <Zap class="h-4 w-4" />
34
- Calculate
38
+ {t.calculate}
35
39
  {:else}
36
40
  <Check class="h-4 w-4" />
37
- Up to date
41
+ {t.upToDate}
38
42
  {/if}
39
43
  </Button>
40
44
  </div>
@@ -1,9 +1,14 @@
1
1
  <script lang="ts">
2
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
3
+
2
4
  interface Props {
3
5
  show: boolean;
4
6
  }
5
7
 
6
8
  let { show }: Props = $props();
9
+
10
+ const locale = getLocaleContext();
11
+ const t = $derived(locale.messages);
7
12
  </script>
8
13
 
9
14
  {#if show}
@@ -19,8 +24,8 @@
19
24
  ></div>
20
25
  </div>
21
26
  <div class="leading-tight flex flex-col">
22
- <span class="text-sm font-semibold">Solving</span>
23
- <span class="text-xs opacity-70">Grasshopper is calculating...</span>
27
+ <span class="text-sm font-semibold">{t.solving}</span>
28
+ <span class="text-xs opacity-70">{t.solvingSubtitle}</span>
24
29
  </div>
25
30
  </div>
26
31
  {/if}
@@ -31,6 +31,22 @@ export interface ViewerMessages {
31
31
  noResultsFor: string;
32
32
  objectFallbackName: string;
33
33
  noMetadata: string;
34
+ loadingSchema: string;
35
+ parametersTab: string;
36
+ moreTab: string;
37
+ expandPanel: string;
38
+ collapsePanel: string;
39
+ /** `{side}` is replaced with `panelSideLeft` / `panelSideRight`. */
40
+ expandSidePanel: string;
41
+ panelSideLeft: string;
42
+ panelSideRight: string;
43
+ tabNoGroups: string;
44
+ solving: string;
45
+ solvingEllipsis: string;
46
+ solvingSubtitle: string;
47
+ pressToCalculate: string;
48
+ calculate: string;
49
+ upToDate: string;
34
50
  }
35
51
  export declare const VIEWER_MESSAGES: Record<Locale, ViewerMessages>;
36
52
  export declare const DEFAULT_LOCALE: Locale;
@@ -40,7 +40,22 @@ const en = {
40
40
  noObjects: 'No objects',
41
41
  noResultsFor: 'No results for "{query}"',
42
42
  objectFallbackName: 'Object',
43
- noMetadata: 'No metadata'
43
+ noMetadata: 'No metadata',
44
+ loadingSchema: 'Loading schema...',
45
+ parametersTab: 'Parameters',
46
+ moreTab: 'More',
47
+ expandPanel: 'Expand panel',
48
+ collapsePanel: 'Collapse panel',
49
+ expandSidePanel: 'Expand {side} panel',
50
+ panelSideLeft: 'left',
51
+ panelSideRight: 'right',
52
+ tabNoGroups: 'This tab has no groups configured.',
53
+ solving: 'Solving',
54
+ solvingEllipsis: 'Solving...',
55
+ solvingSubtitle: 'Grasshopper is calculating...',
56
+ pressToCalculate: 'Press to calculate',
57
+ calculate: 'Calculate',
58
+ upToDate: 'Up to date'
44
59
  };
45
60
  const de = {
46
61
  toolsMenu: 'Viewer-Werkzeuge',
@@ -72,7 +87,22 @@ const de = {
72
87
  noObjects: 'Keine Objekte',
73
88
  noResultsFor: 'Keine Ergebnisse für „{query}“',
74
89
  objectFallbackName: 'Objekt',
75
- noMetadata: 'Keine Metadaten'
90
+ noMetadata: 'Keine Metadaten',
91
+ loadingSchema: 'Schema wird geladen...',
92
+ parametersTab: 'Parameter',
93
+ moreTab: 'Mehr',
94
+ expandPanel: 'Bereich aufklappen',
95
+ collapsePanel: 'Bereich zuklappen',
96
+ expandSidePanel: '{side} Bereich aufklappen',
97
+ panelSideLeft: 'Linken',
98
+ panelSideRight: 'Rechten',
99
+ tabNoGroups: 'Für diesen Tab sind keine Gruppen konfiguriert.',
100
+ solving: 'Berechnung läuft',
101
+ solvingEllipsis: 'Berechnung läuft...',
102
+ solvingSubtitle: 'Grasshopper berechnet...',
103
+ pressToCalculate: 'Zum Berechnen klicken',
104
+ calculate: 'Berechnen',
105
+ upToDate: 'Aktuell'
76
106
  };
77
107
  export const VIEWER_MESSAGES = { en, de };
78
108
  export const DEFAULT_LOCALE = 'en';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selvajs/ui",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "description": "Shared UI components and utilities for Selva applications",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -72,8 +72,8 @@
72
72
  "svelte": "5.55.5",
73
73
  "tailwind-variants": "^3.2.2",
74
74
  "vitest": "^3.2.6",
75
- "@selvajs/schemas": "4.5.0",
76
- "@selvajs/config": "0.0.1"
75
+ "@selvajs/config": "0.0.1",
76
+ "@selvajs/schemas": "4.5.0"
77
77
  },
78
78
  "scripts": {
79
79
  "dev": "vite dev",
@@ -11,6 +11,10 @@
11
11
  import * as Resizable from '$lib/components/primitives/resizable';
12
12
  import { Button } from '$lib';
13
13
  import ParameterPresetManager from './ParameterPresetManager.svelte';
14
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
15
+
16
+ const locale = getLocaleContext();
17
+ const t = $derived(locale.messages);
14
18
 
15
19
  const COLLAPSED_WIDTH = 48;
16
20
 
@@ -74,7 +78,7 @@
74
78
  let isMobile = $state(false);
75
79
  let drawerOpen = $state(false);
76
80
 
77
- const activeLeftTabLabel = $derived(leftTabs[0]?.label ?? 'Parameters');
81
+ const activeLeftTabLabel = $derived(leftTabs[0]?.label ?? t.parametersTab);
78
82
 
79
83
  $effect(() => {
80
84
  const mqMobile = window.matchMedia('(max-width: 639px)');
@@ -200,7 +204,7 @@
200
204
  onclick={() => (drawerOpen = !drawerOpen)}
201
205
  ontouchstart={onDrawerTouchStart}
202
206
  ontouchend={onDrawerTouchEnd}
203
- aria-label={drawerOpen ? 'Collapse panel' : 'Expand panel'}
207
+ aria-label={drawerOpen ? t.collapsePanel : t.expandPanel}
204
208
  aria-expanded={drawerOpen}
205
209
  >
206
210
  <div class="drawer-pill"></div>
@@ -227,7 +231,7 @@
227
231
  <div class="my-4 gap-2 flex items-center">
228
232
  <div class="h-px flex-1 bg-border"></div>
229
233
  <span class="text-xs font-medium px-1 text-muted-foreground">
230
- {rightTabs[0]?.label ?? 'More'}
234
+ {rightTabs[0]?.label ?? t.moreTab}
231
235
  </span>
232
236
  <div class="h-px flex-1 bg-border"></div>
233
237
  </div>
@@ -257,7 +261,7 @@
257
261
  <div class="my-4 gap-2 flex items-center">
258
262
  <div class="h-px flex-1 bg-border"></div>
259
263
  <span class="text-xs font-medium px-1 text-muted-foreground">
260
- {rightTabs[0]?.label ?? 'More'}
264
+ {rightTabs[0]?.label ?? t.moreTab}
261
265
  </span>
262
266
  <div class="h-px flex-1 bg-border"></div>
263
267
  </div>
@@ -1,6 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { TabConfig } from '@selvajs/schemas';
3
3
  import Icon from '@iconify/svelte';
4
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
5
+
6
+ const locale = getLocaleContext();
7
+ const t = $derived(locale.messages);
4
8
 
5
9
  interface Props {
6
10
  side: 'left' | 'right';
@@ -29,7 +33,10 @@
29
33
  style="--collapsed-w: {collapsedWidth}px;"
30
34
  role="button"
31
35
  tabindex="0"
32
- aria-label="Expand {side} panel"
36
+ aria-label={t.expandSidePanel.replace(
37
+ '{side}',
38
+ side === 'left' ? t.panelSideLeft : t.panelSideRight
39
+ )}
33
40
  onclick={onExpand}
34
41
  onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && onExpand()}
35
42
  >
@@ -16,6 +16,8 @@
16
16
  import AppLayout from './AppLayout.svelte';
17
17
  import StateDisplay from '../primitives/StateDisplay.svelte';
18
18
  import { setClientSlot, type ClientSlot } from '../../contexts/clientSlotContext.svelte';
19
+ import type { Locale } from '../../i18n/messages';
20
+ import { setLocaleContext, getLocaleContext } from '../../i18n/localeContext.svelte';
19
21
 
20
22
  import type { Snippet } from 'svelte';
21
23
 
@@ -54,6 +56,13 @@
54
56
  externalScopeKey?: string;
55
57
  // Renders client-sourced inputs with presentation === 'slot'; receives { inputId, displayName, slotLabel, value }.
56
58
  clientSlot?: ClientSlot;
59
+ /**
60
+ * UI language for the app's own chrome (viewer, panels, status text).
61
+ * Provided once here and read by every descendant via locale context.
62
+ * Defaults to English when unset. Does not translate schema-authored labels
63
+ * or Grasshopper-sourced names/metadata.
64
+ */
65
+ lang?: Locale;
57
66
  }
58
67
 
59
68
  let {
@@ -81,13 +90,21 @@
81
90
  header,
82
91
  onReady,
83
92
  externalScopeKey,
84
- clientSlot
93
+ clientSlot,
94
+ lang
85
95
  }: Props = $props();
86
96
 
87
97
  // Make the host's client-input slot available to InputControl deep in the tree.
88
98
  // svelte-ignore state_referenced_locally
89
99
  setClientSlot(clientSlot);
90
100
 
101
+ // Provide the UI locale once for the whole app subtree (viewer, panels, status
102
+ // text). Resolution: explicit `lang` → any host-provided locale → English. The
103
+ // getter is re-read reactively, so switching `lang` updates the chrome live.
104
+ const hostLocale = getLocaleContext();
105
+ setLocaleContext(() => lang ?? hostLocale.locale);
106
+ const t = $derived(getLocaleContext().messages);
107
+
91
108
  const resolvedScopeKey = $derived(externalScopeKey || definitionKey || schema?.id || '');
92
109
 
93
110
  // Solve Session owns the value/lifecycle state machine; the request/response driver
@@ -182,7 +199,7 @@
182
199
  </div>
183
200
  {:else if !schema}
184
201
  <div class="min-h-100 flex items-center justify-center">
185
- <StateDisplay type="loading" size="large" message="Loading schema..." />
202
+ <StateDisplay type="loading" size="large" message={t.loadingSchema} />
186
203
  </div>
187
204
  {:else}
188
205
  {#key definitionKey}
@@ -14,6 +14,10 @@
14
14
  import InputControl from './InputControl.svelte';
15
15
  import OutputDisplay from './OutputDisplay.svelte';
16
16
  import { evaluateGroupVisibility } from '$lib/schema/visibility-rules';
17
+ import { getLocaleContext } from '$lib/i18n/localeContext.svelte';
18
+
19
+ const locale = getLocaleContext();
20
+ const t = $derived(locale.messages);
17
21
 
18
22
  interface Props {
19
23
  tab: TabConfig;
@@ -79,7 +83,7 @@
79
83
  <ScrollArea class="h-full" orientation="vertical">
80
84
  <div class="p-4 tab-content-container">
81
85
  {#if tab.groups.length === 0}
82
- <StateDisplay type="empty" size="medium" message="This tab has no groups configured." />
86
+ <StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
83
87
  {:else}
84
88
  <div class="gap-8 flex flex-col">
85
89
  {#each tab.groups as group (group.id)}
@@ -1,6 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { Button } from './button';
3
3
  import { Zap, Check, Loader, Play } from '@lucide/svelte';
4
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
5
+
6
+ const locale = getLocaleContext();
7
+ const t = $derived(locale.messages);
4
8
 
5
9
  interface Props {
6
10
  hasPendingChanges: boolean;
@@ -25,16 +29,16 @@
25
29
  >
26
30
  {#if isSolving}
27
31
  <Loader class="h-4 w-4 animate-spin" />
28
- Solving...
32
+ {t.solvingEllipsis}
29
33
  {:else if hasNeverSolved}
30
34
  <Play class="h-4 w-4" />
31
- Press to calculate
35
+ {t.pressToCalculate}
32
36
  {:else if hasPendingChanges}
33
37
  <Zap class="h-4 w-4" />
34
- Calculate
38
+ {t.calculate}
35
39
  {:else}
36
40
  <Check class="h-4 w-4" />
37
- Up to date
41
+ {t.upToDate}
38
42
  {/if}
39
43
  </Button>
40
44
  </div>
@@ -1,9 +1,14 @@
1
1
  <script lang="ts">
2
+ import { getLocaleContext } from '../../i18n/localeContext.svelte';
3
+
2
4
  interface Props {
3
5
  show: boolean;
4
6
  }
5
7
 
6
8
  let { show }: Props = $props();
9
+
10
+ const locale = getLocaleContext();
11
+ const t = $derived(locale.messages);
7
12
  </script>
8
13
 
9
14
  {#if show}
@@ -19,8 +24,8 @@
19
24
  ></div>
20
25
  </div>
21
26
  <div class="leading-tight flex flex-col">
22
- <span class="text-sm font-semibold">Solving</span>
23
- <span class="text-xs opacity-70">Grasshopper is calculating...</span>
27
+ <span class="text-sm font-semibold">{t.solving}</span>
28
+ <span class="text-xs opacity-70">{t.solvingSubtitle}</span>
24
29
  </div>
25
30
  </div>
26
31
  {/if}
@@ -52,6 +52,26 @@ export interface ViewerMessages {
52
52
  // Metadata dialog
53
53
  objectFallbackName: string;
54
54
  noMetadata: string;
55
+
56
+ // App layout / compute shell
57
+ loadingSchema: string;
58
+ parametersTab: string;
59
+ moreTab: string;
60
+ expandPanel: string;
61
+ collapsePanel: string;
62
+ /** `{side}` is replaced with `panelSideLeft` / `panelSideRight`. */
63
+ expandSidePanel: string;
64
+ panelSideLeft: string;
65
+ panelSideRight: string;
66
+ tabNoGroups: string;
67
+
68
+ // Solve controls
69
+ solving: string;
70
+ solvingEllipsis: string;
71
+ solvingSubtitle: string;
72
+ pressToCalculate: string;
73
+ calculate: string;
74
+ upToDate: string;
55
75
  }
56
76
 
57
77
  const en: ViewerMessages = {
@@ -87,7 +107,24 @@ const en: ViewerMessages = {
87
107
  noResultsFor: 'No results for "{query}"',
88
108
 
89
109
  objectFallbackName: 'Object',
90
- noMetadata: 'No metadata'
110
+ noMetadata: 'No metadata',
111
+
112
+ loadingSchema: 'Loading schema...',
113
+ parametersTab: 'Parameters',
114
+ moreTab: 'More',
115
+ expandPanel: 'Expand panel',
116
+ collapsePanel: 'Collapse panel',
117
+ expandSidePanel: 'Expand {side} panel',
118
+ panelSideLeft: 'left',
119
+ panelSideRight: 'right',
120
+ tabNoGroups: 'This tab has no groups configured.',
121
+
122
+ solving: 'Solving',
123
+ solvingEllipsis: 'Solving...',
124
+ solvingSubtitle: 'Grasshopper is calculating...',
125
+ pressToCalculate: 'Press to calculate',
126
+ calculate: 'Calculate',
127
+ upToDate: 'Up to date'
91
128
  };
92
129
 
93
130
  const de: ViewerMessages = {
@@ -123,7 +160,24 @@ const de: ViewerMessages = {
123
160
  noResultsFor: 'Keine Ergebnisse für „{query}“',
124
161
 
125
162
  objectFallbackName: 'Objekt',
126
- noMetadata: 'Keine Metadaten'
163
+ noMetadata: 'Keine Metadaten',
164
+
165
+ loadingSchema: 'Schema wird geladen...',
166
+ parametersTab: 'Parameter',
167
+ moreTab: 'Mehr',
168
+ expandPanel: 'Bereich aufklappen',
169
+ collapsePanel: 'Bereich zuklappen',
170
+ expandSidePanel: '{side} Bereich aufklappen',
171
+ panelSideLeft: 'Linken',
172
+ panelSideRight: 'Rechten',
173
+ tabNoGroups: 'Für diesen Tab sind keine Gruppen konfiguriert.',
174
+
175
+ solving: 'Berechnung läuft',
176
+ solvingEllipsis: 'Berechnung läuft...',
177
+ solvingSubtitle: 'Grasshopper berechnet...',
178
+ pressToCalculate: 'Zum Berechnen klicken',
179
+ calculate: 'Berechnen',
180
+ upToDate: 'Aktuell'
127
181
  };
128
182
 
129
183
  export const VIEWER_MESSAGES: Record<Locale, ViewerMessages> = { en, de };