Skip to content

Commit 73b712f

Browse files
fix(app-shell): draw a served app label as served on every Console surface that names an app (#30443)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuXjFYoeducAp4ExBhc3f6eL)_ domain:ui#5 · os-sales · session_01L6bhgVwQZbHRRM9CaXeKif Fixes #30311 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:控制台所有显示应用名的地方(应用切换器、侧栏标题、首页应用卡片/应用条/最近访问、无权限页、AI 对话、应用市场「打开应用」按钮、报表索引)改为直接显示服务端已翻译好的应用名;应用卡片的描述同理。 - 为什么改:客户在 `.custom` 配对包里给已安装应用改名后,服务端(#30353)已按裁定顺序返回新名字,但前端又用基础包的 `apps.NAME.label` 译文盖回去,中文下仍显示旧名。 - 风险与回滚:只改 `ui/packages/app-shell` 的读取顺序;草稿预览(`?preview=draft`)、键式标签、无标签应用仍走前端词条,未改名的应用显示不变。回滚即撤本 PR,无数据迁移。 - 先看:`ui/packages/app-shell/src/hooks/useServedAppLabel.ts`(新 helper 的取值顺序),再看 `layout/AppSwitcher.tsx` 一处调用点。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/hooks/__tests__/useServedAppLabel-30311.test.tsx`,预期 18 条全绿;修复前的取值顺序下其中 5 条是红的。 - 需要你做的:无,席位落地。 ## What changes - New private hook `useServedAppLabel` (`ui/packages/app-shell/src/hooks/useServedAppLabel.ts`). `appLabel(app)` draws a served `label` (a string, or an inline locale map resolved by `metadataText`) as given; `appDescription(app)` does the same for `description`. The bundle (`useObjectLabel().appLabel` / `appDescription`, the `apps.NAME.label` lookup) is asked only for text no server translated: an item stamped `_draft: true` by `?preview=draft`, a keyed `{ key, defaultValue }` label (still resolved through `t`, as before), or an app with no label / description. - Every app-shell surface that named an app through `useObjectLabel().appLabel({ name, label: resolveKeyedI18nLabel(app.label, t) })` now calls `useServedAppLabel().appLabel(app)`: 17 call sites in 11 files, taken from the tree at `fdd06415` (`AppSwitcher` x3, `AiChatPage` x4, `HomePage` x2, `UnifiedSidebar`, `AppHeader` hidden-app menu, `AppNoAccessEmptyState`, `AppCard` label and description, `HomeAppsStrip`, `MarketplacePage`, `MarketplacePackagePage`, `AppReportIndexView`). - `useNavTargetLabel.ts` and `ui/packages/i18n/**` are untouched (claim fences); the hook only imports the exported `metadataText`. - Root changeset `.changeset/30311-served-app-labels.md` (`@object-ui/app-shell` patch). ## Why Since #30353 the server serves an overridden app's label in the ruled order (the `.custom` pair's own text over the base package's translation). The Console handed that served label to the bundle lookup as its fallback, so the base package's `apps.NAME.label` translation won it back and a renamed app kept its old name on zh-CN. The object / view half of #30311 landed in #30388 with the same served-first rule; this is the app half. ## Tests - `ui/packages/app-shell/src/hooks/__tests__/useServedAppLabel-30311.test.tsx` (new, 18 cases): the real hook over the real `useObjectLabel` inside a real `I18nProvider` with a packaged catalogue, en and zh-CN: a renamed served label and description draw as served; an unedited one shows the server's translation; an inline locale map resolves in the active language; controls for a `_draft` item, a keyed label, an absent / empty label and a translation-only description keep the bundle. Plus a census: no non-test source under `app-shell/src` other than the hook destructures `appLabel` / `appDescription` from `useObjectLabel()` (with a positive control that the walk reaches the hook and `AppSwitcher.tsx`, and that the pattern matches the banned shape). ## Acceptance notes - **Evidence branch deviation.** The dispatch asked for screenshots on a never-merged evidence branch; this card thread can push only its designated branch, so the PNGs were committed at `71a58ff3` and removed again in the next commit. The PR squash-merges, so they never reach `main`; the links below point at that commit. - **CommandPalette** already drew `resolveKeyedI18nLabel(app.label, t)` without the bundle, so it showed the served name before this PR and is unchanged. - The live leg exercised the app switcher pill, the sidebar title and Home (app card, recent row app name). The marketplace "Open app" button, the AI chat, the no-access state and the report index's other-app links are pinned by the census and the hook cases, not browser-measured. ## Verification <details><summary>Live, showcase --fresh, zh-CN, Console from ui/ src through Vite, 1440 wide</summary> Override written the way the platform takes it: `POST /api/v1/packages/com.example.showcase/ensure-customization` (pair `com.example.showcase.custom`, created), then `PUT /api/v1/meta/app/showcase_app?package=com.example.showcase.custom&mode=draft` with the served app body, `label: Showcase Hub (renamed-30311)` and `overrides: { package: 'com.example.showcase', basedOn: '0.1.0' }`, then `POST /api/v1/meta/app/showcase_app/publish?package=com.example.showcase.custom` (seq 2). `GET /meta/app/showcase_app` then serves `Showcase Hub (renamed-30311)` under both `Accept-Language: zh-CN` and `en`; the zh-CN catalogue keeps `apps.showcase_app.label` = 功能展示. | Console, zh-CN, same override | `origin/main` app-shell sources (`fdd06415`, restored into the tree, Vite `--force`) | this branch | |:--|:--|:--| | app switcher pill | 功能展示 | Showcase Hub (renamed-30311) | | `/apps/showcase_app` page text matches | renamed 0, 功能展示 1 | renamed 1, 功能展示 0 | | Home matches (app card + recent row) | renamed 0, 功能展示 1 | renamed 2, 功能展示 0 | Screenshots (commit `71a58ff3`): [before app](https://github.com/objectstack-ai/objectstack/blob/71a58ff38d730b7ab0d64e958438deb1b72f68c0/evidence/30311/before-app-1440.png?raw=true) · [after app](https://github.com/objectstack-ai/objectstack/blob/71a58ff38d730b7ab0d64e958438deb1b72f68c0/evidence/30311/after-app-1440.png?raw=true) · [before Home](https://github.com/objectstack-ai/objectstack/blob/71a58ff38d730b7ab0d64e958438deb1b72f68c0/evidence/30311/before-home-1440.png?raw=true) · [after Home](https://github.com/objectstack-ai/objectstack/blob/71a58ff38d730b7ab0d64e958438deb1b72f68c0/evidence/30311/after-home-1440.png?raw=true) </details> <details><summary>Tests, ablation, typecheck, lint (sources at 6f7c06c)</summary> - New pin: `Test Files 1 passed (1)`, `Tests 18 passed (18)`, lock VERDICT command-exit 0. - Ablation 1 (via `scripts/ablation-replace.mjs`, anchor 1 to 0, restored blob == HEAD, `git diff HEAD` empty): the hook's served-text rung replaced by `return undefined` (the old bundle-first order) turns exactly the 5 predicted cells red: renamed label en / zh-CN, inline locale map, renamed description en / zh-CN; `Tests 5 failed | 13 passed (18)`. - Ablation 2: `AppSwitcher.tsx` back on `useObjectLabel()` turns the census red naming `layout/AppSwitcher.tsx`; `Tests 1 failed | 17 passed (18)`; restored, blob == HEAD. - Consumer sweep: every test file under `ui/` that names one of the 11 changed modules or `appLabel` (310 files): `Test Files 310 passed (310)`, `Tests 2598 passed (2598)`, lock VERDICT command-exit 0. - `turbo run type-check --filter=@object-ui/app-shell`: `Tasks: 68 successful, 68 total`, VERDICT command-exit 0. - eslint on the changed files: 0 new errors; the 2 errors it reports (`react-hooks` "Cannot create components during render" in `AppCard.tsx` and `HomeAppsStrip.tsx`) are on `origin/main` unchanged. - `ui/scripts/check-new-cross-file-line-citations.mjs`: 0 new, exit 0. </details> <details><summary>Derived gates (dispatch-gates --commands, head 6f7c06c)</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 36 commands from 14 paths; each was run with its exit code captured before any pipe: all 35 node / pnpm check commands exit 0, and `pnpm --dir ui exec vitest run scripts/` ran under `os-verify-lock` with `--maxWorkers=2` (result in the os-dev-report on #30311). The two evidence commits add and remove the same files, so the derived set is unchanged at the final head. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgX1ZNco5W3wXZFLVbh --- _Generated by [Claude Code](https://claude.ai/code/session_01L6bhgX1ZNco5W3wXZFLVbh)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ab12a3b commit 73b712f

14 files changed

Lines changed: 296 additions & 53 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
---
4+
5+
fix(app-shell): every Console surface that names an app draws the served app label as served
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Now.** The app switcher, the sidebar's app title, the top bar's hidden-app menu, Home's app cards, apps strip, recent rows and setup link, the no-access empty state, the AI chat's app names, the marketplace's "Open app" buttons and the report index's other-app links draw the app `label` (and the app card its `description`) the `/meta/app` read served, as given. The server has already translated it, by its label order for an overridden app: a `.custom` pair's own text is kept over the base package's translation. Before, the served label reached the client's `apps.<name>.label` catalog lookup only as its fallback, so the base package's translation of the old name won.
11+
- **Unchanged.** A pending draft under `?preview=draft`, a keyed label and an app with no label or description are still named by the catalog key, then by the machine name as before. An unedited app shows the same text as before, because the server serves the catalog's translation for it.

‎ui/packages/app-shell/src/console/AppNoAccessEmptyState.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ import { Lock, PanelLeft } from 'lucide-react';
4040
// already reaches it through `PreviewDraftEmptyState`, so the eager graph
4141
// gains no new module.
4242
import { Button, DataEmptyState } from '@object-ui/components';
43-
import { useObjectLabel, useObjectTranslation } from '@object-ui/i18n';
44-
import { resolveKeyedI18nLabel } from '../utils/index.js';
43+
import { useObjectTranslation } from '@object-ui/i18n';
44+
import { useServedAppLabel } from '../hooks/useServedAppLabel.js';
4545

4646
export interface AppNoAccessEmptyStateProps {
4747
/** The active app, as the console's metadata list carries it. */
@@ -65,9 +65,9 @@ export interface AppNoAccessEmptyStateProps {
6565

6666
export function AppNoAccessEmptyState({ app, setupPath, reason = 'no-navigation' }: AppNoAccessEmptyStateProps) {
6767
const { t } = useObjectTranslation();
68-
const { appLabel } = useObjectLabel();
68+
const { appLabel } = useServedAppLabel();
6969
// The same label the header's app switcher shows for this app.
70-
const label = appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) });
70+
const label = appLabel(app);
7171

7272
if (reason === 'menu-only') {
7373
return (

‎ui/packages/app-shell/src/console/ai/AiChatPage.tsx‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
1717
import { useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom';
1818
import { useAuth } from '@object-ui/auth';
19-
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
19+
import { useObjectTranslation } from '@object-ui/i18n';
20+
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
2021
import { toast } from 'sonner';
2122
import { Package as PackageIcon, Sparkles as SparklesIcon } from 'lucide-react';
2223
import { useAdapter } from '../../providers/AdapterProvider.js';
@@ -2181,7 +2182,7 @@ export function ChatPane({
21812182
// Narrow the loosely-typed provider apps once (see MetadataAppItem) so the
21822183
// derivations below read fields without per-access casts.
21832184
const metadataApps = apps as MetadataAppItem[] | undefined;
2184-
const { appLabel } = useObjectLabel();
2185+
const { appLabel } = useServedAppLabel();
21852186
// Fold-insensitive, measured: this walks every message irrespective of role
21862187
// and reads `draftReview`/`builderHandoff`, which the adapter spreads through.
21872188
const boundPackageId = useMemo(
@@ -2202,7 +2203,7 @@ export function ChatPane({
22022203
const app = (metadataApps ?? []).find(
22032204
(a) => a._packageId === boundPackageId,
22042205
);
2205-
return app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : boundPackageId;
2206+
return app ? appLabel(app) : boundPackageId;
22062207
}, [boundPackageId, metadataApps, appLabel, t]);
22072208

22082209
// ADR-0057 A1.b edit mode — the resolved name of the app being EDITED
@@ -2214,7 +2215,7 @@ export function ChatPane({
22142215
const app = (metadataApps ?? []).find(
22152216
(a) => a._packageId === editPackageId,
22162217
);
2217-
return app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : undefined;
2218+
return app ? appLabel(app) : undefined;
22182219
}, [editPackageId, metadataApps, appLabel, t]);
22192220

22202221
// objectui#11658 — the plan card's extend-mode scope chip names the existing
@@ -2223,7 +2224,7 @@ export function ChatPane({
22232224
const resolveAppLabel = useCallback(
22242225
(appName: string) => {
22252226
const app = (metadataApps ?? []).find((a) => a.name === appName);
2226-
return app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : undefined;
2227+
return app ? appLabel(app) : undefined;
22272228
},
22282229
[metadataApps, appLabel, t],
22292230
);
@@ -2387,7 +2388,7 @@ export function ChatPane({
23872388
if (!pkg || byPackage.has(pkg) || isPlatformBuiltinApp(app)) {
23882389
continue;
23892390
}
2390-
byPackage.set(pkg, appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }));
2391+
byPackage.set(pkg, appLabel(app));
23912392
}
23922393
return Array.from(byPackage, ([id, label]) => ({ id, label }));
23932394
}, [metadataApps, appLabel, t]);

‎ui/packages/app-shell/src/console/home/AppCard.tsx‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88

99
import { Star, StarOff, ArrowUpRight } from 'lucide-react';
1010
import { Card, CardContent, Button, Badge } from '@object-ui/components';
11-
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
12-
import { resolveKeyedI18nLabel, appRouteSegment } from '../../utils/index.js';
11+
import { useObjectTranslation } from '@object-ui/i18n';
12+
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
13+
import { appRouteSegment } from '../../utils/index.js';
1314
import { useFavorites } from '../../hooks/useFavorites.js';
1415
import { getIcon } from '../../utils/getIcon.js';
1516
import { cn } from '@object-ui/components';
@@ -40,12 +41,12 @@ function hashStr(s: string): number {
4041

4142
export function AppCard({ app, onClick, isFavorite, index = 0 }: AppCardProps) {
4243
const { t } = useObjectTranslation();
43-
const { appLabel, appDescription } = useObjectLabel();
44+
const { appLabel, appDescription } = useServedAppLabel();
4445
const { toggleFavorite } = useFavorites();
4546

4647
const Icon = getIcon(app.icon);
47-
const label = appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) });
48-
const description = appDescription({ name: app.name, description: resolveKeyedI18nLabel(app.description, t) });
48+
const label = appLabel(app);
49+
const description = appDescription(app);
4950
const accent = ACCENTS[(hashStr(app.name) + index) % ACCENTS.length];
5051

5152
const handleToggleFavorite = (e: React.MouseEvent) => {

‎ui/packages/app-shell/src/console/home/HomeAppsStrip.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ import { useMemo, useState, type ReactNode } from 'react';
4040
import { Link } from 'react-router-dom';
4141
import { LayoutGrid, Plus, Settings, Star } from 'lucide-react';
4242
import { cn } from '@object-ui/components';
43-
import { useDisplayLocale, useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
44-
import { resolveKeyedI18nLabel } from '../../utils/index.js';
43+
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
44+
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
4545
import { appAddressSegment } from '../../utils/appRoute.js';
4646
import { getIcon } from '../../utils/getIcon.js';
4747
import { timeAgo } from '../../utils/relativeTime.js';
@@ -168,7 +168,7 @@ export function HomeAppsStrip({
168168
lastOpened?: ReadonlyMap<string, string>;
169169
}) {
170170
const { t } = useObjectTranslation();
171-
const { appLabel } = useObjectLabel();
171+
const { appLabel } = useServedAppLabel();
172172
const displayLocale = useDisplayLocale();
173173
const [showAll, setShowAll] = useState(false);
174174

@@ -228,7 +228,7 @@ export function HomeAppsStrip({
228228
{/* `-ml-2` puts the first icon under the heading: a tile pads its icon 8 px. */}
229229
<div className="-ml-2 flex flex-wrap gap-1">
230230
{visible.map((app) => {
231-
const label = appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) });
231+
const label = appLabel(app);
232232
const fav = favNames.has(app.name);
233233
const openedAgo = timeAgo(lastOpened?.get(app.name), displayLocale, TILE_OPENED_TIME_FORMAT);
234234
return (

‎ui/packages/app-shell/src/console/home/HomePage.tsx‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ import { useNavTargetLabel, type NavTargetLabelOptions } from '../../hooks/useNa
2424
import { readObjectsByName } from '../../hooks/useObjectPresence.js';
2525
import { useFavorites } from '../../hooks/useFavorites.js';
2626
import { useObjectTranslation } from '@object-ui/i18n';
27-
// Its own line: the line above is the pack-hook binding `perm-home-namespace-3546.test.tsx` reads.
28-
import { useObjectLabel } from '@object-ui/i18n';
27+
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
2928
import { useAuth, useWorkspaceAdminStatus } from '@object-ui/auth';
3029
import { useAgents, isAskAgent, agentHasCapability } from '@object-ui/plugin-chatbot/core';
3130
// May this session author metadata? Shared with the chat surfaces since the
@@ -46,7 +45,6 @@ import {
4645
filterActiveApps,
4746
matchAppBySegment,
4847
resolveHostAppSegment,
49-
resolveKeyedI18nLabel,
5048
} from '../../utils/index.js';
5149
import { useInboxBell } from '../../hooks/useInboxBell.js';
5250
import { openInboxNotification } from '../../layout/openInboxNotification.js';
@@ -364,7 +362,7 @@ export function HomePage() {
364362
// objectstack#29121 — by name: the object a row names is read alone (as its
365363
// icon below is), so Home never loads the whole registry for its rows.
366364
const targetLabel = useNavTargetLabel(BY_NAME);
367-
const { appLabel } = useObjectLabel();
365+
const { appLabel } = useServedAppLabel();
368366
const { favorites } = useFavorites();
369367
const { user } = useAuth();
370368
const { isAdmin, isResolved: isAdminResolved } = useWorkspaceAdminStatus();
@@ -509,7 +507,7 @@ export function HomePage() {
509507
item.type === 'record' && objectName
510508
? targetLabel({ kind: 'object', objectName }) ?? objectName
511509
: recentItemTypeLabel(t, item.type),
512-
app: app ? appLabel({ name: app.name, label: resolveKeyedI18nLabel(app.label, t) }) : undefined,
510+
app: app ? appLabel(app) : undefined,
513511
icon: typeof objectIcon === 'string' && objectIcon !== '' ? objectIcon : undefined,
514512
};
515513
};
@@ -804,7 +802,7 @@ export function HomePage() {
804802
settingsHref={setupApp ? setupPath : undefined}
805803
settingsLabel={
806804
setupApp
807-
? appLabel({ name: setupApp.name, label: resolveKeyedI18nLabel(setupApp.label, t) })
805+
? appLabel(setupApp)
808806
: undefined
809807
}
810808
lastOpened={lastOpened}

‎ui/packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ import {
4646
} from '@object-ui/components';
4747
import { ArrowLeft, ExternalLink, Download, AlertCircle, Package, Trash2, MoreHorizontal, CheckCircle2, ArrowUpCircle, Database, Loader2 } from 'lucide-react';
4848
import { useWorkspaceAdminStatus } from '@object-ui/auth';
49-
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
49+
import { useObjectTranslation } from '@object-ui/i18n';
5050
import { useDisplayLocale } from '@object-ui/i18n';
51+
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
5152
import { useAdapter } from '@object-ui/react';
5253
import { PackageIcon } from './PackageIcon.js';
5354
import { MarkdownText } from './MarkdownText.js';
@@ -84,7 +85,7 @@ import { useDependencyMissingText } from './dependencyMissing.js';
8485
import { getCloudBase, getRuntimeConfig, isMarketplaceEnabled } from '../../runtime-config.js';
8586
import { emitMetadataRefresh } from '../../assistant/assistantBus.js';
8687
import { useMetadata } from '../../providers/MetadataProvider.js';
87-
import { appRouteSegment, filterActiveApps, resolveKeyedI18nLabel } from '../../utils/index.js';
88+
import { appRouteSegment, filterActiveApps } from '../../utils/index.js';
8889
import { waitForServedApp, type ServedApp } from './waitForServedApp.js';
8990
import { SuggestedBindingsPanel, type SuggestedBindingsStrings } from '../../components/SuggestedBindingsPanel.js';
9091
import { ConnectorCredentialsStep } from './ConnectorCredentialsStep.js';
@@ -186,7 +187,7 @@ export function MarketplacePackagePage() {
186187
// The adapter `MetadataProvider` reads through: the deploy wait asks the
187188
// same `GET /meta/app` the cache would, without the cache persisting it.
188189
const adapter = useAdapter();
189-
const { appLabel } = useObjectLabel();
190+
const { appLabel } = useServedAppLabel();
190191
// The runtime's own answer, read once per render -- the same read the
191192
// catalog page makes (objectui#5504). `false` means this runtime mounts no
192193
// marketplace at all, so there is no package to fetch and nothing to
@@ -969,8 +970,8 @@ export function MarketplacePackagePage() {
969970
<Button size="sm" onClick={() => navigate(`/apps/${appRouteSegment(deployApp)}`)}>
970971
{t('marketplace.install.openApp', {
971972
name: appLabel({
973+
...deployApp,
972974
name: String(deployApp.name ?? appRouteSegment(deployApp)),
973-
label: resolveKeyedI18nLabel(deployApp.label, t),
974975
}),
975976
})}
976977
</Button>

‎ui/packages/app-shell/src/console/marketplace/MarketplacePage.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import {
2626
Package, Search, Store, AlertCircle, CheckCircle2, Settings, Loader2, ChevronDown, Copy, Check,
2727
} from 'lucide-react';
2828
import { useWorkspaceAdminStatus } from '@object-ui/auth';
29-
import { useDisplayLocale, useObjectLabel, useObjectTranslation } from '@object-ui/i18n';
29+
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
30+
import { useServedAppLabel } from '../../hooks/useServedAppLabel.js';
3031
import { useAdapter } from '@object-ui/react';
3132
import { PackageIcon } from './PackageIcon.js';
3233
import { MarketplaceAccessDenied } from './MarketplaceAccessDenied.js';
@@ -47,7 +48,7 @@ import {
4748
import { getCloudBase, getRuntimeConfig, isMarketplaceEnabled } from '../../runtime-config.js';
4849
import { PageHeader } from '../../layout/PageHeader.js';
4950
import { emitMetadataRefresh } from '../../assistant/assistantBus.js';
50-
import { appRouteSegment, filterActiveApps, resolveKeyedI18nLabel } from '../../utils/index.js';
51+
import { appRouteSegment, filterActiveApps } from '../../utils/index.js';
5152
import { waitForServedApp, type ServedApp } from './waitForServedApp.js';
5253
import { splitPackageSuffix } from './middleTruncate.js';
5354
import { useDependencyMissingText } from './dependencyMissing.js';
@@ -309,7 +310,7 @@ export function MarketplacePage() {
309310
const { isAdmin, isResolved } = useWorkspaceAdminStatus();
310311
const { t, language } = useObjectTranslation();
311312
const dependencyMissingText = useDependencyMissingText();
312-
const { appLabel } = useObjectLabel();
313+
const { appLabel } = useServedAppLabel();
313314
// The adapter `MetadataProvider` reads through: the deploy wait asks the
314315
// same `GET /meta/app` the cache would, without the cache persisting it.
315316
const adapter = useAdapter();
@@ -619,8 +620,8 @@ export function MarketplacePage() {
619620
<Button size="sm" onClick={() => navigate(`/apps/${appRouteSegment(orgDeployApp)}`)}>
620621
{t('marketplace.install.openApp', {
621622
name: appLabel({
623+
...orgDeployApp,
622624
name: String(orgDeployApp.name ?? appRouteSegment(orgDeployApp)),
623-
label: resolveKeyedI18nLabel(orgDeployApp.label, t),
624625
}),
625626
})}
626627
</Button>

0 commit comments

Comments
 (0)