Skip to content

Commit 1ccb5ba

Browse files
fix(layout): a present navigation label renders verbatim, with no translate-if-equal-to-name exception, and an inline locale-map label renders its text (objectui#11201) (#11297)
Fixes #11201 Clause-②: no Stage 2 of ruling B: the director's ruling `5921167691`, on the ruling record `5910404448` and its execution note `5910537969`. Claim `5921933038`, branch `claude/issue-11201-nav-label-verbatim`. Stage 1 landed as PR #11225 (`cb2f6fb5b`). ## What changes `resolveNavItemLabel` in `packages/layout/src/NavigationRenderer.tsx` now follows the spec's one order and keeps no rule of its own: 1. The id-keyed bundle entry `apps.APP.navigation.ID.label`. `translateApp` applies it upstream, at `/meta`. Untouched here. 2. A present label: an inline locale map reads its locale's entry, and a string renders verbatim. 3. An absent label inherits the target's current, localized label (objectui#9868, unchanged). - **Deleted:** the `isCustomized` arm and its comment. The arm looked a present plain-string label up through the object, view or dashboard resolver when its text equalled the target's machine name (trimmed, case-insensitive). Nothing on `label` matches text against a name now. - **Added:** a present map-valued entry label (`{ en: 'Accounts', 'zh-CN': '客户' }`, which the spec accepts) now reads through the spec's `resolveI18nLabel`. Areas and the designer canvas already use that resolver. The label used to reach the keyed resolver and render empty. - **Kept, but not read:** `resolveObjectLabel`, `resolveDashboardLabel` and `resolveViewLabel` (the props, and the matching positional arguments). They are marked `@deprecated`, so callers compile and get the ruled behaviour. Retiring them is open question 2 in the report. - `packages/i18n/src/useObjectLabel.ts`: the one comment that named the guard now says the guard is gone. Comment only. - Changeset: `@object-ui/layout` patch. It states the visible effect and the one-line fix: clear the entry's label so it inherits, or set the wanted text. No conversion, no notice run. ## Mechanism finding: which locale a map label reads The ruling's map pin says "renders the locale's value". This layer is not given a locale. `NavigationRenderer` has no locale prop, and `@object-ui/layout` has no i18n dependency by design (see the docblock of `resolveAreaLabel` in `AppSchemaRenderer.tsx`). Within the ruled parameters (patch, `Clause-②: no`, no public prop change), the map resolves at the spec resolver's documented no-locale default: its `en` entry, else `default`, else any entry. The area switcher in this package does the same. So in the console, a zh-CN viewer sees the `en` entry of a map-valued nav label. Reaching the viewer's locale needs a new optional prop, with the locale threaded from the console (`UnifiedSidebar` already holds `language`). That widens the public surface, so it is not built here. It is open question 1 in the report. Measured pull: no app navigation in readable sources writes a map-valued entry label. This is the objectstack pointer `5913605784`, re-read at objectstack `5f6b63a6f`: in the files that declare `navigation`, the only object-valued labels are in translation bundles. ## Pins New file `packages/layout/src/__tests__/NavigationRenderer.labelVerbatim-11201.test.tsx`: - **VERBATIM, 4 rows.** A present label equal to its target's machine name (object `account`, view `board`, dashboard `sales_overview`) renders verbatim under `en` and under `zh-CN`. The console's convention resolvers are wired as `UnifiedSidebar` wires them, and none is called. Case and padding variants (`ACCOUNT`, `SALES_OVERVIEW`) stay as written. Control in the same tree: an absent label inherits the target's localized label in each locale, which shows the wiring is live. - **MAP, 2 rows.** Map-valued object, dashboard, group and action labels render the map's text. They never render `[object Object]`, empty text or the target's label. Search finds them by that text. Restated rows in `NavigationRenderer.navLabelOwnership.test.tsx` (they pinned the retired convention): - `still resolves an un-customized object label through resolveObjectLabel` is now `renders a present object label equal to the machine name verbatim, resolveObjectLabel unasked (objectui#11201)`. - `keeps the same guard on the dashboard branch` is now `renders both present dashboard labels verbatim, resolveDashboardLabel unasked (objectui#11201)`. - `still lets an authored object label win over resolveObjectLabel`: the assertion is unchanged. Its comment no longer names the guard. Unchanged and green: `NavigationRenderer.labelInheritsTarget-9868.test.tsx` (absent labels inherit, and an explicit label equal to the machine name stays verbatim with echo resolvers), and the stage-1 pin `useNavigationSync.standardEntryLocalizes-11201.test.tsx`. ## Ablation Run from committed `54656bd7d` with objectstack's `scripts/ablation-replace.mjs`. The layout tests import the renderer by relative path, and the root vitest config aliases `@object-ui/layout` to `src`, so no `dist/` is in the path. - **Leg A: restore the `isCustomized` arm.** Over `packages/layout/src/`: 6 failed, 287 passed. The 6 are exactly the 4 new VERBATIM rows and the 2 restated rows. The MAP rows stay green. - **Leg B: drop the map resolution,** so every object label goes back through the keyed resolver. Over `packages/layout/src/`: 2 failed, 291 passed. The 2 are exactly the MAP rows. - Each leg: the anchor hit once and then zero times, and the blob changed. The restore was proven: blob equals `HEAD`, and `git diff HEAD` is empty. ## Verification All gates ran from the worktree root, on head `54656bd7d`: - `pnpm exec vitest run --reporter=verbose packages/layout/src/`: 30 files, 293 passed. - Consumers outside layout (every test file that imports `NavigationRenderer` or `resolveNavItemLabel`, plus `packages/app-shell/src/layout/`, `packages/app-shell/src/chrome/`, the `nav:menu` render tests, the Studio and designer nav-label pins, `packages/plugin-designer/` and `packages/i18n/src/`): 173 files, 1966 passed, 13 skipped. - Closure build `turbo run build --filter=@object-ui/layout^... --filter=@object-ui/i18n^... --concurrency=2`: 8 of 8 successful. Then `type-check` for `@object-ui/layout` and `@object-ui/i18n`: exit 0. `tsc -p tsconfig.test.json --listFilesOnly` lists both touched layout test files. - `eslint` on the 4 changed source and test files: 0 errors and 28 warnings, all pre-existing. Linting the base blobs gives `NavigationRenderer.tsx` 25 warnings against 23 at head (two `as any` casts went with the arm), and the other files the same counts. - `pnpm exec vitest run scripts/__tests__/` (the whole root suite, because a changeset `.md` is added): 177 files passed and 2 skipped; 5371 tests passed and 2 skipped; lock VERDICT command-exit 0. - `check-changeset-presence` exit 0. `check:control-bytes` exit 0. `check:new-line-citations` reports 0 new citations, exit 0. `markdown-test-inputs --audit` exit 0. `check-changeset-no-major` exit 0. `check:changeset-claims` is report-only: it names 3 pending changesets that cite a touched file, and I read each paragraph; none is falsified by this diff. ## Acceptance notes - **Fork clause: not triggered.** No objectui producer on `main` writes a machine-name label. The console sync effect passes no label. The Studio skeleton and inspector bind and the wizard's `generatedEntryLabel` were fixed by stage 1. Remaining `label: d.name`-shaped writes are the Studio rail's object list and a dashboard document seed, not nav entries. - **Declared-type gap, measured, reported (not filed):** `@object-ui/types` declares a nav entry's `label` as `string` (the type and the zod mirror), narrower than the spec's `I18nLabel`. `safeValidateSchema`, which `objectui validate` calls, refuses a map-valued nav label (`navigation[0].label`: expected string, received object). The spec's `NavigationItemSchema` accepts the same entry. The new map pin casts its fixtures for that reason, and says so. - The header comment of `packages/app-shell/src/views/nav-menu-renderer.tsx` still says its labels get "the same convention-based object / view / dashboard i18n resolution the sidebar gets". That is no longer true for any label. `UnifiedSidebar` and `nav-menu-renderer` still build the three unread resolvers. Both files are outside this claim's file surface; they belong with open question 2. - `AppSchemaRenderer.tsx`'s `resolveAreaLabel` docblock quotes the old `resolveObjectLabel` prop doc ("enables convention-based i18n auto-resolution …"), which this PR rewrote. The docblock's point, injection over an i18n dependency, still stands. - `resolveNavItemLabel` still resolves objectui's keyed `{ key, defaultValue }` reference through `t`, unchanged. The spec's nav label refuses that shape and objectui's type does not declare it. The new code tells it apart from a locale map by the two member names a map can never carry. --- _Generated by [Claude Code](https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent d79f525 commit 1ccb5ba

5 files changed

Lines changed: 352 additions & 111 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/layout': patch
3+
---
4+
5+
fix(layout): a present navigation label renders as written, with no exception, and an inline locale-map label renders its text (objectui#11201)
6+
7+
A navigation entry's text now follows one rule. A present label is shown as written. An absent label
8+
shows the current, localized label of what the entry opens.
9+
10+
- **Visible change.** An entry whose stored label is its target's machine name (for example
11+
`account`, `sales_overview`) used to be translated, because the sidebar looked that text up as
12+
the object's, view's or dashboard's name. It now shows the name as written, in every locale.
13+
**To fix such an entry:** clear its label so it inherits the target's localized label, or set the
14+
text you want shown. Stored navigation is not converted, and no notice is sent.
15+
- An entry label written as an inline locale map (`{ en: 'Accounts', 'zh-CN': '客户' }`) used to
16+
render as empty text. It now renders the map's text. The sidebar is not told the viewer's locale,
17+
so it reads the `en` entry, then `default`, then any entry, as it already does for an area label.
18+
- `NavigationRenderer`'s `resolveObjectLabel`, `resolveDashboardLabel` and `resolveViewLabel` props,
19+
and the matching arguments of `resolveNavItemLabel`, are no longer read. They are still accepted,
20+
so no caller breaks. An unlabelled entry's localized text comes from `resolveTargetLabel`.

‎packages/i18n/src/useObjectLabel.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,11 @@ export function useObjectLabel() {
376376
* props could never fire — the renderer's `isCustomized` guard compared a
377377
* node's authored label against its own `id` (`Workspace` vs
378378
* `grp_workspace`), which never match, so the guard was true for every
379-
* real entry. The props are gone; the promise was false, not merely
380-
* unused. This helper is kept as a plain key reader for a consumer that
381-
* renders navigation itself — it has no first-party caller.
379+
* real entry. The props are gone, and so is the guard itself
380+
* (objectui#11201: the renderer no longer matches a label's text against
381+
* any name); the promise was false, not merely unused. This helper is kept
382+
* as a plain key reader for a consumer that renders navigation itself — it
383+
* has no first-party caller.
382384
*/
383385
navGroupLabel: (appName: string, groupId: string, fallback: string) =>
384386
resolve(`apps.${appName}.navigation.${groupId}.label`, fallback),

‎packages/layout/src/NavigationRenderer.tsx‎

Lines changed: 92 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ import {
6666
useIsMobile,
6767
} from '@object-ui/components';
6868
import type { NavigationItem, KeyedI18nLabel } from '@object-ui/types';
69+
// Aliased on import, following PR #4169's convention (as `AppSchemaRenderer`
70+
// does): this file has its OWN `resolveLabel` over the KEYED vocabulary, and
71+
// the spec's resolver reads the INLINE locale map — neither accepts the other's
72+
// shape.
73+
import { resolveI18nLabel as resolveInlineI18nLabel } from '@objectstack/spec/ui';
6974

7075
// ---------------------------------------------------------------------------
7176
// Types
@@ -219,34 +224,26 @@ export interface NavigationRendererProps {
219224
onReorder?: (reorderedItems: NavigationItem[]) => void;
220225

221226
/**
222-
* Optional label resolver for object-type navigation items.
223-
* When provided, called with `(objectName, fallbackLabel)` for items
224-
* where `item.type === 'object'` and `item.label` is a plain string.
225-
* Enables convention-based i18n auto-resolution without coupling
226-
* the layout package to i18n.
227+
* @deprecated Not consulted since objectui#11201 (ruling B). It was the
228+
* object half of the renderer's translate-if-equal-to-name convention: a
229+
* present plain-string label whose text equalled the object's machine name
230+
* was looked up through it. A present label now renders verbatim, and an
231+
* absent one inherits through {@link resolveTargetLabel}, which is where an
232+
* object's localized name comes from. Passing it changes nothing.
227233
*/
228234
resolveObjectLabel?: (objectName: string, fallbackLabel: string) => string;
229235

230236
/**
231-
* Optional label resolver for dashboard-type navigation items.
232-
* Called with `(dashboardName, fallbackLabel)` for items where
233-
* `item.type === 'dashboard'` and `item.label` is a plain string.
234-
* Mirrors `resolveObjectLabel` for the convention-based i18n hook
235-
* `useObjectLabel().dashboardLabel`.
237+
* @deprecated Not consulted since objectui#11201 (ruling B) — the dashboard
238+
* half of the retired convention; see {@link resolveObjectLabel}.
236239
*/
237240
resolveDashboardLabel?: (dashboardName: string, fallbackLabel: string) => string;
238241

239242
/**
240-
* Optional label resolver for object-type navigation items that target a
241-
* specific view (i.e. `viewName` is set). Called with
242-
* `(objectName, viewName, fallbackLabel)`. Mirrors
243-
* `useObjectLabel().viewLabel` and resolves
244-
* `{ns}.objects.{objectName}._views.{viewName}.label`.
245-
*
246-
* Without this resolver, an object item with a `viewName` falls back to
247-
* its schema-provided explicit label (which keeps it distinct from a bare
248-
* object-list entry under the same group — avoids visual duplicates such
249-
* as two `商机` rows where one is the list and the other is a Kanban view).
243+
* @deprecated Not consulted since objectui#11201 (ruling B) — the view half
244+
* of the retired convention; see {@link resolveObjectLabel}. An unlabelled
245+
* view entry still shows the VIEW's label (not the parent object's), through
246+
* {@link resolveTargetLabel}.
250247
*/
251248
resolveViewLabel?: (objectName: string, viewName: string, fallbackLabel: string) => string;
252249

@@ -261,15 +258,19 @@ export interface NavigationRendererProps {
261258
resolveTargetLabel?: NavTargetLabelResolver;
262259

263260
// RETIRED (`9c60144b5`): `resolveGroupLabel` / `resolveItemLabel`, the two
264-
// id-keyed label resolvers. They were unreachable by construction — see the
265-
// note on `resolveNavItemLabel` below — and app-navigation localization is
266-
// owned solely by the server-side `/meta` boundary. Do not re-add them; a
267-
// sidebar label that needs translating is translated there.
261+
// id-keyed label resolvers. They were unreachable by construction: they sat
262+
// behind a text comparison of the label against the node's own `id`
263+
// (`Workspace` vs `grp_workspace`), which never matched. App-navigation
264+
// localization is owned solely by the server-side `/meta` boundary (rung 1
265+
// of `resolveNavItemLabel`'s order). Do not re-add them; a sidebar label that
266+
// needs translating is translated there.
268267

269268
/**
270-
* Optional i18n translation function for resolving I18nLabel objects
271-
* (`{ key, defaultValue }`). When provided, labels are translated
272-
* through i18next; otherwise falls back to `defaultValue`.
269+
* Optional i18n translation function for resolving KEYED label objects
270+
* (`{ key, defaultValue }`, see {@link resolveLabel}). When provided, labels
271+
* are translated through i18next; otherwise falls back to `defaultValue`.
272+
* An inline locale map (`{ en, 'zh-CN' }`) is not keyed and never reaches
273+
* it — {@link resolveNavItemLabel} reads a map itself.
273274
*/
274275
t?: (key: string, options?: any) => string;
275276

@@ -329,30 +330,32 @@ export function resolveLabel(
329330
}
330331

331332
/**
332-
* Resolve a navigation item label, applying:
333-
* 1. i18n translation for I18nLabel objects (when `t` is provided)
334-
* 2. Convention-based i18n for object-type items whose plain string label was
335-
* never customized (still equal to the bare object/dashboard/item name),
336-
* so standard nav entries still localize automatically
337-
* (when `resolveObjectLabel`/`resolveDashboardLabel`/etc. is provided)
338-
* 3. Otherwise, the schema-authored explicit label always wins — an app
339-
* author who wrote a custom label (e.g. a plural 'Projects') must never
340-
* have it silently overridden by an `objects.<name>.label` translation.
333+
* Resolve a navigation item's display text, in the spec's one order
334+
* (`@objectstack/spec` `BaseNavItemSchema.label`, objectstack#20849):
341335
*
342-
* Deliberately NOT here: id-keyed resolution for `group` items and for
343-
* url/page/report/custom leaves. Those two hooks existed until `9c60144b5`
344-
* and could never fire: the `isCustomized` guard below compares the authored
345-
* label against the branch's comparison target, and on an id-keyed branch
346-
* that target is the node's own `id` (`grp_workspace`) while the label is its
347-
* text (`Workspace`). Those never compare equal, so the guard was true for
348-
* every real entry and the resolver under it was dead code with a live
349-
* docstring promising localization.
336+
* 1. The id-keyed bundle entry `apps.APP.navigation.ID.label`. That rung runs
337+
* UPSTREAM of this function, at the server-side `/meta` boundary:
338+
* `translateApp` in `@objectstack/spec` (`src/system/i18n-resolver.ts`)
339+
* rewrites a node's `label` by its `id` before the metadata reaches this
340+
* renderer. App-navigation localization has that one owner — localize nav
341+
* labels there, never here.
342+
* 2. A PRESENT label, as authored ({@link presentNavItemLabel}): an inline
343+
* locale map reads its locale's entry, and a string renders verbatim.
344+
* 3. An ABSENT label inherits its target's CURRENT label (objectui#9868 —
345+
* `@objectstack/spec` 17.5.0 made it optional, cloud#2021 letter-A ruling),
346+
* resolved here at render time and never written back — see
347+
* {@link inheritedNavItemLabel} for the ladder. That arm is keyed on
348+
* ABSENCE alone, so an authored label is never swapped for the target's
349+
* metadata label, however it is spelled.
350350
*
351-
* App-navigation localization is owned solely by the server-side `/meta`
352-
* boundary: `translateApp` in `@objectstack/spec`
353-
* (`src/system/i18n-resolver.ts`) rewrites every navigation node's `label` by
354-
* id before the metadata reaches this renderer, so `base` is already
355-
* localized when it arrives. One owner, not two — localize nav labels there.
351+
* ⛔ Nothing here matches a label's text against a name (objectui#11201,
352+
* ruling B). A present label equal to its target's machine name (`account`)
353+
* renders `account` in every locale: text cannot tell a deliberate `account`
354+
* from a machine-written one, so translation is keyed on identity (rung 1) or
355+
* comes through inheritance (rung 3). The three convention resolvers
356+
* (`resolveObjectLabel` / `resolveDashboardLabel` / `resolveViewLabel`) that
357+
* the retired rule consulted are still accepted in their positions, so no
358+
* caller breaks, and are not read.
356359
*
357360
* EXPORTED since `969ba84f4`, for the same reason {@link resolveHref} is: a
358361
* second surface now renders the same `NavigationItem[]`. `nav:menu` is the
@@ -363,59 +366,53 @@ export function resolveLabel(
363366
* under two names on two surfaces of one app, which is exactly the drift the
364367
* "single source of truth" note on `resolveHref` exists to prevent. Nothing
365368
* about the behaviour changed with the keyword.
366-
*
367-
* ABSENT `label` (objectui#9868 — `@objectstack/spec` 17.5.0 made it optional,
368-
* cloud#2021 letter-A ruling): the entry inherits its target's CURRENT label,
369-
* resolved here at render time and never written back — see
370-
* {@link inheritedNavItemLabel} for the ladder. That arm is keyed on ABSENCE
371-
* alone: the steps above never consult `targetLabel`, so an authored label is
372-
* never swapped for the target's metadata label, however it is spelled.
373369
*/
374370
export function resolveNavItemLabel(
375371
item: NavigationItem,
376-
resolver?: (objectName: string, fallbackLabel: string) => string,
372+
_objectResolver?: (objectName: string, fallbackLabel: string) => string,
377373
t?: (key: string, options?: any) => string,
378-
dashboardResolver?: (dashboardName: string, fallbackLabel: string) => string,
379-
viewResolver?: (objectName: string, viewName: string, fallbackLabel: string) => string,
374+
_dashboardResolver?: (dashboardName: string, fallbackLabel: string) => string,
375+
_viewResolver?: (objectName: string, viewName: string, fallbackLabel: string) => string,
380376
targetLabel?: NavTargetLabelResolver,
381377
): string {
382378
// A separator carries no `label` (objectui#10867): there is nothing to name.
383379
if (item.type === 'separator') return '';
384-
// Absent ⇒ inherit (objectui#9868). Resolved BEFORE `resolveLabel`, which
385-
// takes only a present label.
380+
// Absent ⇒ inherit (objectui#9868).
386381
if (item.label === undefined) return inheritedNavItemLabel(item, targetLabel);
387-
const base = resolveLabel(item.label, t);
388-
// Only apply convention-based resolution for items with plain string labels.
389-
// I18nLabel objects (with explicit key/defaultValue) already have their own translation keys.
390-
if (typeof item.label !== 'string') return base;
391-
// An explicit label that differs from the bare target name was authored on
392-
// purpose (e.g. a custom plural 'Projects') — never let convention-based
393-
// i18n resolution override it.
394-
const isCustomized = (target: string | undefined) =>
395-
!!target && base.trim().toLowerCase() !== target.trim().toLowerCase();
396-
if (item.type === 'object' && item.objectName) {
397-
// View-scoped item — prefer view-specific label so a Kanban / Calendar /
398-
// custom view in the sidebar doesn't collapse to the parent object's
399-
// label (which would visually duplicate the object's list entry).
400-
// Convention: `{ns}.objects.{objectName}._views.{viewName}.label`.
401-
if (item.viewName) {
402-
if (isCustomized(item.viewName)) return base;
403-
if (viewResolver) return viewResolver(item.objectName, item.viewName, base);
404-
// No view resolver: respect the schema-provided explicit label rather
405-
// than overriding with the parent object's i18n label.
406-
return base;
407-
}
408-
if (isCustomized(item.objectName)) return base;
409-
if (resolver) return resolver(item.objectName, base);
410-
}
411-
if (item.type === 'dashboard' && (item as any).dashboardName) {
412-
if (isCustomized((item as any).dashboardName)) return base;
413-
if (dashboardResolver) return dashboardResolver((item as any).dashboardName, base);
414-
}
415-
// `group` items and non-object/non-dashboard leaves (url, page, report,
416-
// custom) return the authored label untouched — their localization already
417-
// happened at the server `/meta` boundary (see the note above).
418-
return base;
382+
return presentNavItemLabel(item.label, t);
383+
}
384+
385+
/**
386+
* The text of a PRESENT navigation label, as authored (rung 2 of
387+
* {@link resolveNavItemLabel}'s order).
388+
*
389+
* - A string renders verbatim.
390+
* - An inline locale map — the spec's `I18nLabel`, `{ en, 'zh-CN' }` — reads
391+
* through the spec's own `resolveI18nLabel`, the resolver areas and the
392+
* Studio's designer canvas use for the same vocabulary. This layer is handed
393+
* no locale (it carries no i18n dependency by design — see
394+
* `resolveAreaLabel` in `AppSchemaRenderer.tsx`), so the map resolves at
395+
* that resolver's documented no-locale default: its `en` entry, else
396+
* `default`, else any entry — the floor the area switcher settles for.
397+
* A map with no text at all reads `''`: it is present, so it inherits
398+
* nothing.
399+
* - objectui's KEYED reference `{ key, defaultValue?, params? }` resolves
400+
* through `t` ({@link resolveLabel}), as before. The two object shapes do
401+
* not overlap: the spec's inline-locale key pattern excludes both `key` and
402+
* `defaultValue`.
403+
*/
404+
function presentNavItemLabel(
405+
label: unknown,
406+
t: ((key: string, options?: any) => string) | undefined,
407+
): string {
408+
if (typeof label === 'string') return label;
409+
if (isKeyedLabel(label)) return resolveLabel(label, t);
410+
return resolveInlineI18nLabel(label as Parameters<typeof resolveInlineI18nLabel>[0], undefined) ?? '';
411+
}
412+
413+
/** objectui's keyed label reference, told apart from an inline locale map by the two member names a map can never carry. */
414+
function isKeyedLabel(label: unknown): label is KeyedI18nLabel {
415+
return typeof label === 'object' && label !== null && ('key' in label || 'defaultValue' in label);
419416
}
420417

421418
/**
@@ -1360,7 +1357,8 @@ function NavigationItemRenderer({
13601357
const Icon = resolveIcon(item.icon);
13611358
// Through `resolveNavItemLabel`, not `resolveLabel`: an action entry's
13621359
// `label` may be absent too (objectui#9868), and that is where the absent
1363-
// arm lives. For a present label the two answer identically on this type.
1360+
// arm lives — as is the inline-locale-map read (objectui#11201), which
1361+
// `resolveLabel` does not do.
13641362
const actionLabel = resolveNavItemLabel(item, resolveObjectLabel, tProp, resolveDashboardLabel, resolveViewLabel, resolveTargetLabel);
13651363
return (
13661364
<SidebarMenuItem>

0 commit comments

Comments
 (0)