Skip to content

Commit 416e96f

Browse files
fix(app-shell): resolve inline locale-map view and page labels; a map view label no longer blanks the Console (#25997)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9DkV74BzcJoE2chDHKmDXba)_ Fixes #25901 Clause-②: no Metadata-protocol: none ## What was wrong `@objectstack/spec` types a list view's `label`, and a page's `label` / `description`, as `I18nLabel`: a string or an inline locale map. Two Console readers did not handle the map. 1. **View labels (p1, measured).** `viewTabLabel` returned `view.label` untouched. On the served branch it was drawn as given. On the other branch it was handed to the bundle's `viewLabel` as the fallback, and the bundle returns the fallback unchanged on a miss. The server does not stop a map from reaching it: `translateObjectListViews` / `translateView` only overlay catalog text, so a map with no `_views.KEY.label` entry is served as authored. On a `/view/NAME` route the app header rendered that object as a React child and threw `Objects are not valid as a React child (found: object with keys {en, zh-CN})`. `AppHeader` is mounted by `ConsoleLayout`, outside the only route `ErrorBoundary` in `AppContent`, so the throw unmounted the whole Console. That held for a served view and for a client-derived one alike. Measurement posted on the card before any code: #25901 (comment) The same value also fed the object page's tab bar, the mobile view switcher and the record page's way back. 2. **Interface list page (p2).** The heading and the record drawer's title read `typeof page.label === 'string' ? page.label : page.name`, so a map label showed the machine name. A map `description` was dropped. ## The fix (producer side) - `viewTabLabel` (`ui/packages/app-shell/src/views/viewTabLabel.ts`) takes the viewer's `language` and resolves the label with `metadataText` before either branch. This is the app-shell helper objectstack-ai/objectui#13501 used. The served/derived split is unchanged: a served view's resolved text is drawn as given, and any other view's resolved text becomes `viewLabel`'s fallback, so a catalog entry still wins there. A map that resolves to no text counts as no `label` and is drawn as the object's plural label, like a label-less view. - Its five call sites pass `language` from `useObjectTranslation()`, the same channel the header's other `metadataText` titles use, so the header and the tab agree. Four are in `ObjectView.tsx`, in the view-tab regions only (mobile switcher, record way back twice, tab bar), plus the hook destructure. The fifth is the `AppHeader.tsx` view-route title. - `InterfaceListPage` resolves `page.label` and `page.description` with `metadataText`. The machine name is the heading, and `cfg.source || 'Record'` the drawer title, only when no label resolves. - `plugin-view`'s `ViewTabBar` is **not** changed. Its `ViewTabItem.label` is typed `string`, and the producer now honours that type. ## Tests - `AppHeader.inlineViewLabel-25901.test.tsx`: a served map view and a derived map view, each in `en` and `zh-CN`, plus an empty map, with an empty bundle so no catalog entry can stand in for the map. - `viewTabLabel.inlineLabel-25901.test.ts`: the served branch never asks the bundle; the derived branch hands the bundle the resolved text; a catalog entry still wins; an empty or blank map gives the unlabelled text; string controls. - `InterfaceListPage.inlineLabel-25901.test.tsx`: the real `MetadataProvider` under a real `I18nProvider`. Map heading and description in `en` and `zh-CN`, an empty map falls back to the machine name, and a string control. - **Ablation** (fix committed first; `viewTabLabel.ts` and `InterfaceListPage.tsx` restored to base `0c372288`, with a trap restoring from `HEAD`): `12 failed | 4 passed (16)`. The 4 that stayed green are the controls (catalog wins, string control, empty-map machine-name fallback, string page). Restore was proven by an empty `git diff HEAD`. - Local: the three new files `16 passed (16)`. At head `615a9c05`, the 119 app-shell test files whose names match `ObjectView` / `AppHeader` / `InterfaceListPage` / `viewTabLabel` / `ServedView` / `servedLabels`: `Test Files 119 passed (119)`, `Tests 995 passed (995)`. **Declared narrowing:** the whole app-shell suite (1725 files) did not finish in 40 minutes under the shared verify lock and was stopped, so it is left to CI (`Test Core`). - `pnpm --filter @object-ui/app-shell type-check` (both `tsc` projects) exit 0. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 34 families at `615a9c05`. All 34 ran locally with exit 0. `--ran` reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, with every exit code recorded). Everything else is CI's. ## Acceptance notes - `ObjectView`'s delete-view confirmation interpolates `targetView?.label || vid` into its prompt. A map label there would read `[object Object]`. It is reachable only for saved (`sys_view`-backed) views, whose labels come from the Console's own add and rename inputs as strings. This is outside the view-tab regions and was left alone. Carrier: none. - `AppHeader` sits outside every error boundary, so any render throw in the header blanks the whole Console, not just the route. This PR removes the trigger that was measured. The boundary placement itself is unchanged. Noted, not filed. Carrier: none. - Scope of the measurement: the header crash was rendered (harness above). The tab-bar, mobile-switcher and record way-back surfaces were read from the code, not rendered, and share the fixed producer. --- _Generated by [Claude Code](https://claude.ai/code/session_01WVwANq56oyBZtcDWbWGfva)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d727348 commit 416e96f

8 files changed

Lines changed: 474 additions & 15 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): a list view or interface page whose `label` is an inline locale map is named in the viewer's language, and a map view label no longer takes the Console down
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **View labels.** `ListViewSchema` types a view's `label` as `I18nLabel`, and the server overlays only catalog text, so a map with no `_views` catalog entry reaches the Console as authored. `viewTabLabel` returned that map untouched. The app header then rendered it as a React child on a `/view/NAME` route and threw. The header sits outside the route's error boundary, so the whole Console went blank. The object page's tab bar, the mobile view switcher and the record page's way back read the same value. `viewTabLabel` now resolves the map in the viewer's language first, so all of them draw a string. A map that resolves to no text is drawn as the object's plural label, like a view with no `label`.
11+
- **Interface list pages.** A page whose `label` is a map showed its machine name as the heading and as the record drawer's title, and a map `description` was dropped. Both now resolve in the viewer's language. The machine name is used only when no label resolves.
12+
13+
What to do: nothing. Metadata that already carries inline locale-map labels now renders as authored.

‎ui/packages/app-shell/src/layout/AppHeader.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,7 @@ export function AppHeader({
756756
servedViews,
757757
viewLabel,
758758
objectPluralLabel(currentObject),
759+
language,
759760
);
760761
}
761762
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* #25901 — on a `/apps/APP/OBJECT/view/NAME` route, a view whose `label` is an
5+
* inline locale map is titled in the viewer's language.
6+
*
7+
* ## The defect
8+
*
9+
* `ListViewSchema` types a view's `label` as `I18nLabel`: a string or an
10+
* inline locale map. The server overlays only catalog text
11+
* (`translateObjectListViews`), so a map with no `_views.KEY.label` catalog
12+
* entry is served as authored. `viewTabLabel` handed it on untouched — drawn
13+
* as given on the served branch, offered to the bundle as the fallback on the
14+
* other — and the header rendered the object as a React child. The header is
15+
* mounted outside the route's error boundary, so the throw unmounted the whole
16+
* console. Measured before the change with this file's harness: both cells
17+
* below threw `Objects are not valid as a React child (found: object with keys
18+
* {en, zh-CN})` and left the body empty.
19+
*
20+
* ## The rule
21+
*
22+
* `viewTabLabel` resolves the label with `metadataText` in the viewer's
23+
* language before either branch, so every caller — this header, the tab bar,
24+
* the mobile switcher, the record page's way back — receives a string. A map
25+
* that resolves to no text counts as no label: the object's plural label.
26+
*
27+
* Harness: `AppHeader.labelLessViewTitle-12805`'s, with an empty translation
28+
* bundle so no catalog entry can stand in for the map.
29+
*
30+
* Directions, written before the run: the map cells RED before the change (the
31+
* throw), GREEN after; the empty-map cell draws the plural label.
32+
*/
33+
import '@testing-library/jest-dom/vitest';
34+
import { describe, it, expect, vi, afterEach } from 'vitest';
35+
import { render, screen, cleanup } from '@testing-library/react';
36+
37+
const route = vi.hoisted(() => ({ pathname: '/apps/showcase_app/recheck_gate/view/default' }));
38+
const meta = vi.hoisted(() => ({ value: null as any }));
39+
40+
vi.mock('react-router-dom', () => ({
41+
useLocation: () => ({ pathname: route.pathname, search: '', hash: '', state: null, key: 't' }),
42+
useParams: () => ({ appName: 'showcase_app' }),
43+
useNavigate: () => vi.fn(),
44+
useSearchParams: () => [new URLSearchParams(), vi.fn()] as const,
45+
Link: ({ children, to, ...p }: any) => <a href={String(to)} {...p}>{children}</a>,
46+
}));
47+
48+
vi.mock('@object-ui/components', async (importOriginal) => {
49+
const actual = await importOriginal<Record<string, unknown>>();
50+
const stripProps = (p: any) => {
51+
const { asChild, variant, size, align, sideOffset, onOpenChange, ...rest } = p ?? {};
52+
return rest;
53+
};
54+
const Pass = ({ children, ...p }: any) => <div {...stripProps(p)}>{children}</div>;
55+
return {
56+
...actual,
57+
Button: ({ children, asChild, variant, size, ...p }: any) => (
58+
<button type="button" {...p}>{children}</button>
59+
),
60+
DropdownMenu: Pass,
61+
DropdownMenuTrigger: Pass,
62+
DropdownMenuContent: () => null,
63+
DropdownMenuItem: () => null,
64+
DropdownMenuLabel: () => null,
65+
DropdownMenuSeparator: () => null,
66+
DropdownMenuGroup: Pass,
67+
Avatar: Pass,
68+
AvatarImage: () => null,
69+
AvatarFallback: Pass,
70+
Popover: Pass,
71+
PopoverTrigger: Pass,
72+
PopoverContent: () => null,
73+
Tabs: Pass,
74+
TabsList: Pass,
75+
TabsTrigger: ({ children }: any) => <button type="button">{children}</button>,
76+
TabsContent: Pass,
77+
cn: (...c: any[]) => c.filter(Boolean).join(' '),
78+
};
79+
});
80+
81+
vi.mock('lucide-react', () => {
82+
const Icon = () => <span />;
83+
return new Proxy({ __esModule: true } as Record<string | symbol, unknown>, {
84+
get: (target, prop) => {
85+
if (prop === 'then' || prop === '__esModule' || typeof prop === 'symbol') return target[prop];
86+
return Icon;
87+
},
88+
has: (_target, prop) => prop !== 'then',
89+
});
90+
});
91+
92+
vi.mock('@object-ui/react', async (importOriginal) => ({
93+
...(await importOriginal<Record<string, unknown>>()),
94+
useOffline: () => ({ isOnline: true }),
95+
}));
96+
vi.mock('@object-ui/collaboration', async (importOriginal) => ({
97+
...(await importOriginal<Record<string, unknown>>()),
98+
PresenceAvatars: () => null,
99+
useTenantPresence: () => [],
100+
}));
101+
vi.mock('../ModeToggle', () => ({ ModeToggle: () => null }));
102+
vi.mock('../LocaleSwitcher', () => ({ LocaleSwitcher: () => null }));
103+
vi.mock('../ConnectionStatus', () => ({ ConnectionStatus: () => null }));
104+
vi.mock('../AppSwitcher', () => ({ AppSwitcher: () => null }));
105+
vi.mock('../LocalizedSidebarTrigger', () => ({ LocalizedSidebarTrigger: () => null }));
106+
vi.mock('../PreviewBadge', () => ({ PreviewBadge: () => null }));
107+
vi.mock('../WorkspaceSwitcher', () => ({ WorkspaceSwitcher: () => null }));
108+
109+
// ONE module-scope fetch double, never torn down: the header's pollers fire and
110+
// forget, and may read after a test body returns (see the sibling suites).
111+
vi.stubGlobal(
112+
'fetch',
113+
vi.fn(async () =>
114+
new Response(JSON.stringify({ data: [], items: [], agents: [], requests: [] }), {
115+
status: 200,
116+
headers: { 'Content-Type': 'application/json' },
117+
}),
118+
),
119+
);
120+
121+
// The header reads its lists through this module; the served-view question goes
122+
// to the same context through `@object-ui/react` (provided below) — one value.
123+
vi.mock('../../providers/MetadataProvider', () => ({ useMetadata: () => meta.value }));
124+
vi.mock('../../context/NavigationContext.js', () => ({
125+
useNavigationContext: () => ({ currentAppName: 'showcase_app', recordTitle: undefined }),
126+
}));
127+
vi.mock('@object-ui/auth', async (importOriginal) => {
128+
// ONE stable identity — a fresh closure per render re-arms every
129+
// `[getAuthConfig]` effect forever (see the sibling suites).
130+
const getAuthConfig = () => Promise.resolve({ features: { multiOrgEnabled: false } });
131+
return {
132+
...(await importOriginal<Record<string, unknown>>()),
133+
useAuth: () => ({
134+
user: { id: 'u1', name: 'Zhang San', email: 'zs@example.com' },
135+
signOut: vi.fn(),
136+
isAuthEnabled: true,
137+
organizations: [],
138+
activeOrganization: null,
139+
isOrganizationsLoading: false,
140+
switchOrganization: vi.fn(),
141+
getAuthConfig,
142+
}),
143+
getUserInitials: () => 'ZS',
144+
useWorkspaceAdminStatus: () => ({ isAdmin: true, isResolved: true }),
145+
};
146+
});
147+
const fakeAdapter = { find: () => Promise.resolve({ data: [] }), getClient: () => undefined };
148+
vi.mock('../../providers/AdapterProvider', () => ({ useAdapter: () => fakeAdapter }));
149+
150+
import { I18nProvider } from '@object-ui/i18n';
151+
import { MetadataCtx } from '@object-ui/react';
152+
import { AppHeader } from '../AppHeader';
153+
154+
afterEach(cleanup);
155+
156+
const OBJECT_NAME = 'recheck_gate';
157+
const PLURAL = 'QA Gates';
158+
const SERVED_LABEL = { en: 'My gates', 'zh-CN': '我的关卡' } as const;
159+
const DERIVED_LABEL = { en: 'Derived gates', 'zh-CN': '派生关卡' } as const;
160+
161+
/** No catalog entry for any view: the map is the only text there is. */
162+
const BUNDLE = { en: {}, 'zh-CN': {} };
163+
164+
/**
165+
* The `/meta/object` answer embeds `served_map` (a served view) and
166+
* `empty_map`; the object as `MetadataProvider` merges it also carries
167+
* `derived_map`, which no server read served.
168+
*/
169+
function world() {
170+
const doc = {
171+
name: OBJECT_NAME,
172+
label: 'QA Gate',
173+
pluralLabel: PLURAL,
174+
listViews: {
175+
served_map: { type: 'grid', label: SERVED_LABEL },
176+
empty_map: { type: 'grid', label: {} },
177+
},
178+
};
179+
const objects = [{ ...doc, listViews: { ...doc.listViews, derived_map: { type: 'grid', label: DERIVED_LABEL } } }];
180+
const objectAnswer = [doc];
181+
meta.value = {
182+
apps: [],
183+
objects,
184+
dashboards: [],
185+
reports: [],
186+
pages: [],
187+
loading: false,
188+
error: null,
189+
refresh: async () => {},
190+
invalidate: () => {},
191+
ensureType: async () => [],
192+
getItem: async () => null,
193+
getItemsByType: (type: string) => (type === 'object' ? objectAnswer : []),
194+
getTypeStatus: () => 'ready',
195+
};
196+
return objects;
197+
}
198+
199+
function renderAt(pathname: string, language: 'en' | 'zh-CN', objects: any[]) {
200+
route.pathname = pathname;
201+
return render(
202+
<I18nProvider config={{ defaultLanguage: language, detectBrowserLanguage: false, resources: BUNDLE }} persistLanguage={false}>
203+
<MetadataCtx.Provider value={meta.value}>
204+
<AppHeader variant="app" appName="showcase_app" activeAppName="showcase_app" objects={objects} />
205+
</MetadataCtx.Provider>
206+
</I18nProvider>,
207+
);
208+
}
209+
210+
const LANGUAGES = ['en', 'zh-CN'] as const;
211+
212+
describe('AppHeader — a view labelled with an inline locale map is titled in the viewer\'s language (#25901)', () => {
213+
it.each(LANGUAGES)('%s: a served view', (language) => {
214+
renderAt(`/apps/showcase_app/${OBJECT_NAME}/view/served_map`, language, world());
215+
216+
expect(screen.getAllByText(SERVED_LABEL[language]).length).toBeGreaterThan(0);
217+
});
218+
219+
it.each(LANGUAGES)('%s: a view no server read served', (language) => {
220+
renderAt(`/apps/showcase_app/${OBJECT_NAME}/view/derived_map`, language, world());
221+
222+
expect(screen.getAllByText(DERIVED_LABEL[language]).length).toBeGreaterThan(0);
223+
});
224+
225+
it('a map that resolves to no text is titled by the object\'s plural label', () => {
226+
renderAt(`/apps/showcase_app/${OBJECT_NAME}/view/empty_map`, 'en', world());
227+
228+
expect(screen.getAllByText(PLURAL).length).toBeGreaterThan(0);
229+
});
230+
});

‎ui/packages/app-shell/src/views/InterfaceListPage.tsx‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { isSystemManagedField } from '@object-ui/types';
3535
import { leadWithNameField } from '@object-ui/core';
3636
import type { ActionBarSchema, ListViewSchema } from '@object-ui/types';
3737
import { useMetadata } from '../providers/MetadataProvider.js';
38+
import { metadataText } from '../hooks/useNavTargetLabel.js';
3839
import { useTenancyPosture } from '../hooks/useTenancyPosture.js';
3940
import { useNavigationContext } from '../context/NavigationContext.js';
4041
import { matchAppBySegment } from '../utils/index.js';
@@ -270,7 +271,7 @@ export function defaultMapFromObject(objectDef: any): { locationField: string }
270271
}
271272

272273
export function InterfaceListPage({ page, className, onConfigChange, reserveEditAffordance }: InterfaceListPageProps) {
273-
const { t } = useObjectTranslation();
274+
const { t, language } = useObjectTranslation();
274275
// objectui#12980 — the source object, read by name, not the whole registry:
275276
// reading `objects` would load every object to find one. Inside an app that
276277
// names a package it rides the app's scope, as `nav:menu`'s capability gate
@@ -640,15 +641,21 @@ export function InterfaceListPage({ page, className, onConfigChange, reserveEdit
640641
// declare is refused here.
641642
const buttonBar: ActionBarSchema = { type: 'action:bar', location: 'list_toolbar', actions: buttonActions, size: 'sm', variant: 'outline' };
642643

644+
// The page's `label` and `description` are `I18nLabel`s — a string or an
645+
// inline locale map — resolved in the viewer's language (#25901). Only a page
646+
// whose label resolves to no text falls back to its machine name.
647+
const pageLabel = metadataText(page.label, language);
648+
const pageDescription = metadataText(page.description, language);
649+
643650
return (
644651
<div className={className ?? 'h-full flex flex-col'} data-testid="interface-list-page">
645652
<div className={`pl-4 pt-4 pb-2 shrink-0 flex items-start justify-between gap-3 ${reserveEditAffordance ? 'pr-12' : 'pr-4'}`}>
646653
<div className="min-w-0">
647654
<h1 className="text-lg font-semibold leading-tight">
648-
{typeof page.label === 'string' ? page.label : page.name}
655+
{pageLabel ?? page.name}
649656
</h1>
650-
{typeof page.description === 'string' && page.description && (
651-
<p className="text-sm text-muted-foreground mt-0.5">{page.description}</p>
657+
{pageDescription && (
658+
<p className="text-sm text-muted-foreground mt-0.5">{pageDescription}</p>
652659
)}
653660
</div>
654661
{buttonActions.length > 0 && (
@@ -672,7 +679,7 @@ export function InterfaceListPage({ page, className, onConfigChange, reserveEdit
672679
<NavigationOverlay
673680
{...navOverlay}
674681
setIsOpen={(o: boolean) => { if (!o) closeRecordDrawer(); }}
675-
title={typeof page.label === 'string' ? page.label : (cfg.source || 'Record')}
682+
title={pageLabel ?? (cfg.source || 'Record')}
676683
>
677684
{(record: any) => (
678685
<RecordDetailView

‎ui/packages/app-shell/src/views/ObjectView.tsx‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,7 +1475,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
14751475
const [searchParams, setSearchParams] = useSearchParams();
14761476
const location = useLocation();
14771477
const { showDebug } = useMetadataInspector();
1478-
const { t } = useObjectTranslation();
1478+
const { t, language } = useObjectTranslation();
14791479
const { objectLabel, objectPluralLabel, objectDescription: objectDesc, viewLabel, viewEmptyState, actionParamText, fieldLabel, fieldOptionLabel } = useObjectLabel();
14801480
// The views a server read served — `/meta/view`, and the object document's
14811481
// own `listViews` — already translated: their labels are drawn as given,
@@ -2477,11 +2477,11 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
24772477
const Icon = VIEW_TYPE_ICONS[view.type as keyof typeof VIEW_TYPE_ICONS];
24782478
return {
24792479
id: view.id,
2480-
label: viewTabLabel(view, objectDef.name, servedViews, viewLabel, unlabelledViewText),
2480+
label: viewTabLabel(view, objectDef.name, servedViews, viewLabel, unlabelledViewText, language),
24812481
icon: Icon ? <Icon className="h-4 w-4" /> : undefined,
24822482
};
24832483
});
2484-
}, [views, objectDef.name, viewLabel, unlabelledViewText, servedViews]);
2484+
}, [views, objectDef.name, viewLabel, unlabelledViewText, servedViews, language]);
24852485
useMobileViewSwitcherRegistration({
24862486
views: mobileViewSwitcherItems,
24872487
activeViewId: activeViewId ?? '',
@@ -2878,7 +2878,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
28782878
const originState = {
28792879
from: {
28802880
pathname: location.pathname + (location.search || ''),
2881-
label: viewTabLabel({ id: activeView?.id, name: activeView?.name, label: activeView?.label }, objectDef.name, servedViews, viewLabel, unlabelledViewText),
2881+
label: viewTabLabel({ id: activeView?.id, name: activeView?.name, label: activeView?.label }, objectDef.name, servedViews, viewLabel, unlabelledViewText, language),
28822882
},
28832883
};
28842884
if (viewId) {
@@ -2887,7 +2887,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
28872887
navigate(`record/${encodeURIComponent(String(recordId))}`, { state: originState });
28882888
}
28892889
},
2890-
[navigate, viewId, location.pathname, location.search, objectDef, activeView?.id, activeView?.name, activeView?.label, viewLabel, unlabelledViewText, servedViews]
2890+
[navigate, viewId, location.pathname, location.search, objectDef, activeView?.id, activeView?.name, activeView?.label, viewLabel, unlabelledViewText, servedViews, language]
28912891
);
28922892
/**
28932893
* The list surface's half of the record-link mechanism (objectui#4490):
@@ -3015,7 +3015,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
30153015
const isSystem = !saved;
30163016
return {
30173017
id: view.id,
3018-
label: viewTabLabel(view, objectDef.name, servedViews, viewLabel, unlabelledViewText),
3018+
label: viewTabLabel(view, objectDef.name, servedViews, viewLabel, unlabelledViewText, language),
30193019
type: view.type,
30203020
hasActiveFilters: Array.isArray(view.filter) && view.filter.length > 0,
30213021
hasActiveSort: Array.isArray(view.sort) && view.sort.length > 0,
@@ -3881,7 +3881,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
38813881
const originState = {
38823882
from: {
38833883
pathname: location.pathname + (location.search || ''),
3884-
label: viewTabLabel({ id: activeView?.id, name: activeView?.name, label: activeView?.label }, objectDef.name, servedViews, viewLabel, unlabelledViewText),
3884+
label: viewTabLabel({ id: activeView?.id, name: activeView?.name, label: activeView?.label }, objectDef.name, servedViews, viewLabel, unlabelledViewText, language),
38853885
},
38863886
};
38873887
if (viewId) {
@@ -3891,7 +3891,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }: Co
38913891
}
38923892
}
38933893
},
3894-
}), [objectDef, onEdit, activeViewSearch, activeViewFilter, activeViewSort, activeView?.id, activeView?.name, activeView?.label, navigate, viewId, isAdmin, location.pathname, location.search, viewLabel, unlabelledViewText, servedViews]);
3894+
}), [objectDef, onEdit, activeViewSearch, activeViewFilter, activeViewSort, activeView?.id, activeView?.name, activeView?.label, navigate, viewId, isAdmin, location.pathname, location.search, viewLabel, unlabelledViewText, servedViews, language]);
38953895

38963896
// The header's primary action, drawn the same in both of the header's
38973897
// faces (below). [#5153] `objectCanCreate && createVisible` — the

0 commit comments

Comments
 (0)