Skip to content

Commit 3ab9136

Browse files
fix(studio): the landing page names the .custom customization route, not the environment overlay (#29537)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbBNo2iwVzPjRLwtsGTXAWgt)_ Fixes #29489 Clause-②: no Metadata-protocol: none The two lines are copied from the claim (comment 6583617139). ## 维护者速读 - 改了什么:Studio 首页(App Builder)的页面说明和"已安装(只读)"区的提示语,英文与中文两份,不再叫用户"用环境覆盖定制"只读包,改为指向现有路径:打开条目,用「自定义」或「新建类似」,修改存进该包配对的 .custom 定制包,升级仍到达已安装的包。 - 为什么改:ADR-0146 D11 之后,不带包的写入落到已打包条目会被 403 NOT_OVERRIDABLE 拒绝并点名 .custom 配对包;首页教的第一条路正是被拒的那条。 - 风险与回滚:只改两个文案值(en、zh)和 BuilderLanding.tsx 里两段注释,不加不删键,没有行为改动。回滚即撤销本 PR。 - 先看:`ui/packages/i18n/src/namespaces/studio/en.ts` 的 `engine.studio.landing.description` 与 `engine.studio.landing.readonlyHint`;重钉的 `BuilderLanding.environmentOverlayCopy-12171.test.ts`;新测试 `BuilderLanding.customizeCopy-29489.test.tsx`。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/views/studio-design/BuilderLanding.customizeCopy-29489.test.tsx packages/app-shell/src/views/studio-design/BuilderLanding.environmentOverlayCopy-12171.test.ts`,预期 6 个用例全过(换回 main 的文案时其中 3 个转红)。 - 需要你做的:无,席位落地。 ## What changes - **`engine.studio.landing.description`** (en, zh): the page description's last clause said read-only packages are customized "with an environment overlay". It now says their items are changed with Customize or New similar, which save into the package's `.custom` customization package. - **`engine.studio.landing.readonlyHint`** (en, zh): the read-only section's hint said "Customize one with an environment overlay, for the metadata types that accept one". It now says: open an item and choose Customize or New similar; the change is saved in the package's `.custom` customization package, and upgrades keep reaching the installed package. The comment above the en row names the routes that exist now. - **`BuilderLanding.tsx`**: two code comments that still described a read-only package as customized by environment overlay now name its `.custom` pair. Comments only; no code changes. - The words are the tables' own: the Customize and New similar action labels (`engine.studio.customize.action`, `engine.studio.auto.newSimilar.action`; zh 「自定义」, 「新建类似」), and zh 定制包 as in the landing's existing customization-package strings. No key is added or removed. - **Where the keys live:** only in the Studio namespace tables (`ui/packages/i18n/src/namespaces/studio/en.ts` and `zh.ts`). None of the ten `locales/*.ts` packs carries `engine.studio.landing.*`, so the locale-pack parity tests do not read them, and only en and zh change. ## Tests - **Re-pinned `BuilderLanding.environmentOverlayCopy-12171.test.ts`.** Same population (every designer table `readDesignerPairs` reads that carries the keys) and same reader control. The org-axis pin still covers all three keys. The pin that required every value to name the environment overlay is split: the two landing keys must name no environment overlay / 环境覆盖, and must name the `.custom` package and the table's own Customize label; `perm.readOnly.artifact.hint` keeps its environment-overlay assertion, which is now the matcher's lit control (that hint names the overlay because it says the overlay is refused). - **New `BuilderLanding.customizeCopy-29489.test.tsx`.** Renders the real `BuilderLanding` with a read-only package in en and in zh (through `I18nProvider`, the zh studio catalogue preloaded at module scope). It reads the description and the hint off the page, checks they are the tables' rows, and checks each names `.custom`, Customize and New similar, with no overlay phrase. - **Red on main:** with both tables restored to the main blob (`git restore --source=c88467d4`, mutation counted on disk: old en phrase 1, old zh phrase 1, new en phrase 0), 3 cases go red: the 12171 landing pin and both 29489 locale cases. Restored with `git checkout HEAD --`, `git diff HEAD` empty, both blob hashes equal HEAD. ## Acceptance notes - **Does any metadata type still take a package-less environment overlay?** Not as a route the landing can offer. Read on `main` c88467d: `packagelessCloseSubject` (`packages/metadata-protocol/src/protocol-packageless-overlay-migration.ts`) closes the package-less save, publish, delete and rollback onto an item one read-only code package ships, wherever `os migrate packageless-overlays` could move its row, with `403 NOT_OVERRIDABLE` naming the pair (`packagelessOverlayWriteRefusal`). It stays open only for rows that ceremony leaves: an item more than one package ships, a base with no pair id, a stored row declaring a foreign override or shadowed by the pair, an `email_template` locale the base does not ship, or a base that records no version. A package-less row for a name no read-only package ships is environment metadata, not an overlay. The pin is the `metadata-protocol` suite "an item a CODE package ships: the package-less write is closed (ADR-0146 D11)" in `protocol.packageless-save-address.test.ts`. Not confirmed live on a running server. - **Noted, not changed:** `view` and `dashboard` (and `report`, `translation`, `email_template`) still declare `allowEnvironmentOverlay: true` in `DEFAULT_METADATA_TYPE_REGISTRY` (`packages/spec/src/kernel/metadata-plugin.zod.ts`). On the write, the D11 close wins: ADR-0146 D11 keeps the flag as the predicate the migration reads and the read path honours for an unmigrated row, not as a write door. The spec stays off this PR. - **Noted, not changed:** `engine.studio.pkg.readonlyLink` ("Read-only · Customize with an overlay", zh 「只读 · 用覆盖定制」) is no longer rendered anywhere; only `StudioReadOnlyLink.overlay-27356.test.tsx` reads it, as the label that must be absent. It is a dead row teaching the closed route, invisible to users, and outside this card's surface. - Docs and the turned-off sentence are #29374's and are untouched here. ## Verification All on head f1c366f (base c88467d), in worktree `objectstack-issue-29489`. <details><summary>Tests</summary> - `pnpm --dir ui exec vitest run --maxWorkers=2` over the 2 touched test files: 2 files, 6 tests passed. - The same, over every app-shell and console test naming `BuilderLanding`, `studio.landing`, `readDesignerPairs` or `zh-base-term` (37 files, including `zh-base-term.i18n-13131.test.ts` and the `StudioRoute.*` console tests), plus `packages/i18n/`: 132 files passed, 1706 tests passed, 13 skipped. - `pnpm --dir ui exec vitest run scripts/`: 169 files passed, 2 skipped; 4921 tests passed. - `pnpm exec turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n`: 66 of 66 tasks successful (app-shell runs `tsc --noEmit` and `tsc -p tsconfig.test.json`). - `pnpm exec eslint` over the five touched ui files: exit 0. </details> <details><summary>Gates</summary> - `node scripts/pm/dispatch-gates.mjs --commands`: 34 derived families. All 34 ran and exited 0, including `check-sdui-manifest.mjs` and its `--self-test`, `check-changeset-no-major`, `check-adr-0087-registration`, `check-closing-keyword-parity`, `check-empty-changeset`, `check:published-files` and `check:cross-package-test-inputs`. - `dispatch-gates --ran`: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. - `pnpm --dir ui check:new-line-citations`: 0 new citations. - Dogfood battery: n/a, because there is no `!` in the title and no (narrowing) arm. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jTSExv2so3yrUvy5Qz)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8ce974c commit 3ab9136

6 files changed

Lines changed: 202 additions & 31 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
fix(i18n): the Studio landing page tells users to customize a read-only package in its `.custom` customization package, not with an environment overlay
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The App Builder landing page's description (`engine.studio.landing.description`) and its read-only section hint (`engine.studio.landing.readonlyHint`) told users to customize a code or installed package "with an environment overlay". A write naming no package onto an item a read-only package ships is now refused with `403 NOT_OVERRIDABLE`, and the refusal names the package's `.custom` pair. Both strings, in English and Chinese, now name the route that is open: open an item and choose Customize or New similar; the change is saved in the package's `.custom` customization package, and upgrades keep reaching the installed package.
11+
12+
Only the two values change. No key is added or removed, and nothing else on the page changes.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* objectstack#29489 — the Studio landing teaches the customization route that
5+
* is open, not the package-less overlay objectstack ADR-0146 D11 closed.
6+
*
7+
* A package-less write onto an item a read-only package ships answers 403
8+
* NOT_OVERRIDABLE and names the base's `.custom` pair. The landing's page
9+
* description and its read-only section hint used to send the user there
10+
* ("customize them with an environment overlay"). They now name Customize and
11+
* New similar, saved in the package's `.custom` customization package.
12+
*
13+
* Read off the real `BuilderLanding`, rendered in English and in Chinese with a
14+
* read-only package listed, so both strings are on the page. The expected
15+
* words are the tables' own: the Customize and New similar action labels.
16+
*/
17+
18+
import '@testing-library/jest-dom/vitest';
19+
import * as React from 'react';
20+
import { afterEach, describe, expect, it, vi } from 'vitest';
21+
import { cleanup, render, screen } from '@testing-library/react';
22+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
23+
import { I18nProvider, createI18n } from '@object-ui/i18n';
24+
import { t } from '../metadata-admin/i18n';
25+
import { initRuntimeConfig, resetRuntimeConfigForTesting } from '../../runtime-config';
26+
// The landing's rows live in `@object-ui/i18n`'s `studio` namespace, whose `zh`
27+
// catalogue is fetched on demand: resident before any case.
28+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
29+
await loadStudioCatalogue('zh');
30+
31+
const packages = vi.hoisted(() => ({ list: [] as Array<Record<string, unknown>> }));
32+
33+
vi.mock('./packages-io', async (importOriginal) => {
34+
const mod = await importOriginal<typeof import('./packages-io')>();
35+
return { ...mod, fetchPackages: vi.fn(async () => packages.list) };
36+
});
37+
38+
// The create dialog is closed throughout; its form stack is not under test.
39+
vi.mock('../metadata-admin/PackageFormDialog', () => ({ PackageFormDialog: () => null }));
40+
41+
import { BuilderLanding } from './BuilderLanding';
42+
43+
const READ_ONLY = { id: 'com.example.showcase', name: 'Showcase', writable: false, namespace: 'showcase' };
44+
45+
type Locale = 'en-US' | 'zh-CN';
46+
const LOCALES: ReadonlyArray<readonly [Locale, 'en' | 'zh']> = [
47+
['en-US', 'en'],
48+
['zh-CN', 'zh'],
49+
];
50+
51+
/** The retired route, in both languages the tables speak. */
52+
const ENVIRONMENT_OVERLAY = /environment overlay|环境覆盖/iu;
53+
54+
afterEach(() => {
55+
cleanup();
56+
packages.list = [];
57+
vi.unstubAllGlobals();
58+
resetRuntimeConfigForTesting();
59+
});
60+
61+
/** The row for `key` in `locale`, which must exist: `t` echoes a missing key. */
62+
function row(key: string, locale: Locale): string {
63+
const text = t(key, locale);
64+
expect(text, `no ${locale} row for ${key}`).not.toBe(key);
65+
return text;
66+
}
67+
68+
async function renderLanding(lang: 'en' | 'zh') {
69+
resetRuntimeConfigForTesting();
70+
vi.stubGlobal(
71+
'fetch',
72+
vi.fn(async () => {
73+
const body = { features: { installLocal: false, marketplace: false } };
74+
return { ok: true, status: 200, json: async () => body, text: async () => JSON.stringify(body) } as Response;
75+
}),
76+
);
77+
await initRuntimeConfig();
78+
packages.list = [READ_ONLY];
79+
const landing = (
80+
<MemoryRouter initialEntries={['/studio']}>
81+
<Routes>
82+
<Route path="/studio" element={<BuilderLanding />} />
83+
</Routes>
84+
</MemoryRouter>
85+
);
86+
if (lang === 'en') return render(landing);
87+
const zh = createI18n({ defaultLanguage: 'zh', detectBrowserLanguage: false, resources: { zh: {} } });
88+
return render(<I18nProvider instance={zh}>{landing}</I18nProvider>);
89+
}
90+
91+
/** The page description: the paragraph right under the title row. */
92+
function descriptionElement(): HTMLElement {
93+
const titleRow = screen.getByRole('heading', { level: 1 }).parentElement;
94+
const description = titleRow?.nextElementSibling;
95+
if (!(description instanceof HTMLElement)) throw new Error('no description under the landing title');
96+
return description;
97+
}
98+
99+
describe('Studio landing: a read-only package is customized in its .custom package (objectstack#29489)', () => {
100+
for (const [locale, lang] of LOCALES) {
101+
it(`${locale}: the description and the read-only hint name Customize, New similar and the .custom package, never an overlay`, async () => {
102+
await renderLanding(lang);
103+
await screen.findByText('Showcase');
104+
105+
const customize = row('engine.studio.customize.action', locale);
106+
const newSimilar = row('engine.studio.auto.newSimilar.action', locale);
107+
const shown = [
108+
descriptionElement().textContent ?? '',
109+
screen.getByTestId('studio-landing-readonly-hint').textContent ?? '',
110+
];
111+
112+
// The rendered text is the table's row, so the landing reads these keys.
113+
expect(shown).toEqual([
114+
row('engine.studio.landing.description', locale),
115+
row('engine.studio.landing.readonlyHint', locale),
116+
]);
117+
for (const text of shown) {
118+
expect(text).not.toMatch(ENVIRONMENT_OVERLAY);
119+
expect(text).toContain('.custom');
120+
expect(text).toContain(customize);
121+
expect(text).toContain(newSimilar);
122+
}
123+
});
124+
}
125+
});

‎ui/packages/app-shell/src/views/studio-design/BuilderLanding.environmentOverlayCopy-12171.test.ts‎

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* objectui#12171 — the Studio landing names the overlay axis that exists.
4+
* objectui#12171 — the Studio landing names the overlay axis that exists;
5+
* objectstack#29489 — and the landing no longer offers that overlay at all.
56
*
67
* ADR-0131 D6 (objectstack) retired ADR-0005's per-organization overlay axis.
78
* The registry flag the landing points at is `allowEnvironmentOverlay`: may an
@@ -12,6 +13,12 @@
1213
* (`perm.readOnly.artifact.hint`), said "per-org overlay" (zh 「按组织覆盖」)
1314
* in its parenthetical; the card's claim amendment added it to this pin.
1415
*
16+
* objectstack ADR-0146 D11 then closed the package-less write onto a packaged
17+
* item (403 NOT_OVERRIDABLE, naming the base's `.custom` pair). The landing's
18+
* two strings now teach the route that is open: Customize or New similar,
19+
* saved in the package's `.custom` customization package. The permission hint
20+
* still names the environment overlay, because it says that overlay is refused.
21+
*
1522
* ## The population
1623
*
1724
* Every designer string table, read through their shared extractor
@@ -23,10 +30,14 @@
2330
*
2431
* 1. No value of these keys, in any table that carries them, names the
2532
* organization axis.
26-
* 2. Lit control on the matcher: every one of those values names the
27-
* environment overlay. A table in a language neither pattern speaks fails
28-
* here, so pin 1 cannot pass on it by matching nothing.
29-
* 3. Lit control on the reader: for every locale the module's own
33+
* 2. No landing value names the environment overlay, and every landing value
34+
* names the route that exists: the `.custom` package and the table's own
35+
* Customize label, read from the same table, so the check speaks every
36+
* language a table is written in.
37+
* 3. Lit control on the matchers: the permission hint names the environment
38+
* overlay in every table. A table in a language neither pattern speaks
39+
* fails here, so pins 1 and 2 cannot pass on it by matching nothing.
40+
* 4. Lit control on the reader: for every locale the module's own
3041
* `SupportedLocale` union declares, the value `t()` serves is the one the
3142
* extractor read, so an extractor that read some other table cannot pass.
3243
*/
@@ -49,21 +60,27 @@ const readTables: (root: string) => Map<string, Map<string, string>> = readDesig
4960
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../..');
5061

5162
/**
52-
* The keys that name the overlay axis: the two `BuilderLanding` renders, and
53-
* the hint `PermissionMatrixEditor` shows on a set a code package ships. The
54-
* third landing string, the overlay link's tooltip, retired with the link: the
63+
* The two strings `BuilderLanding` renders about a read-only package. The third
64+
* landing string, the overlay link's tooltip, retired with the link: the
5565
* landing no longer links into Setup (objectstack ADR-0146 D1, N-28).
5666
*/
57-
const KEYS = [
58-
'engine.studio.landing.description',
59-
'engine.studio.landing.readonlyHint',
60-
'perm.readOnly.artifact.hint',
61-
] as const;
67+
const LANDING_KEYS = ['engine.studio.landing.description', 'engine.studio.landing.readonlyHint'] as const;
68+
69+
/** The hint `PermissionMatrixEditor` shows on a set a code package ships: it names the overlay it refuses. */
70+
const PERMISSION_HINT = 'perm.readOnly.artifact.hint';
71+
72+
const KEYS = [...LANDING_KEYS, PERMISSION_HINT] as const;
73+
74+
/** The Customize action's own label, read from each table beside the landing strings. */
75+
const CUSTOMIZE_ACTION = 'engine.studio.customize.action';
76+
77+
/** The customization package a read-only package is paired with (objectstack ADR-0146 D3). */
78+
const CUSTOM_PAIR = /\.custom\b/u;
6279

6380
/** The retired axis, in the languages the tables speak: "org", "per-org", "organization", 组织. */
6481
const ORGANIZATION_AXIS = /\borg(?:ani[sz]ation)?s?\b|组织/iu;
6582

66-
/** The axis that exists, in the same languages. */
83+
/** The overlay axis, in the same languages: refused on a packaged item since objectstack ADR-0146 D11. */
6784
const ENVIRONMENT_OVERLAY = /\benvironment overlay\b|环境覆盖/iu;
6885

6986
/**
@@ -77,7 +94,7 @@ function tablesCarryingKeys(): Array<[string, Map<string, string>]> {
7794
return [...readTables(REPO_ROOT)].filter(([, table]) => KEYS.some((key) => table.has(key)));
7895
}
7996

80-
describe('the overlay copy names the environment overlay, not the organization axis (objectui#12171)', () => {
97+
describe('the read-only copy names no organization axis, and the landing offers no overlay (objectui#12171, objectstack#29489)', () => {
8198
it('reads the tables t() serves', () => {
8299
const read = tablesCarryingKeys();
83100
for (const locale of Object.keys(DECLARED_LOCALES)) {
@@ -104,14 +121,30 @@ describe('the overlay copy names the environment overlay, not the organization a
104121
expect(offenders).toEqual([]);
105122
});
106123

107-
it('every value of these keys names the environment overlay', () => {
108-
const silent: string[] = [];
124+
it('no landing value offers the environment overlay; each names Customize and the .custom package', () => {
125+
const offenders: string[] = [];
109126
for (const [name, table] of tablesCarryingKeys()) {
110-
for (const key of KEYS) {
127+
const customize = table.get(CUSTOMIZE_ACTION);
128+
if (!customize) {
129+
offenders.push(`${name} ${CUSTOMIZE_ACTION}: missing`);
130+
continue;
131+
}
132+
for (const key of LANDING_KEYS) {
111133
const value = table.get(key) ?? '';
112-
if (!ENVIRONMENT_OVERLAY.test(value)) silent.push(`${name} ${key}: ${value}`);
134+
if (ENVIRONMENT_OVERLAY.test(value)) offenders.push(`${name} ${key} offers the overlay: ${value}`);
135+
if (!CUSTOM_PAIR.test(value)) offenders.push(`${name} ${key} names no .custom package: ${value}`);
136+
if (!value.includes(customize)) offenders.push(`${name} ${key} names no "${customize}": ${value}`);
113137
}
114138
}
139+
expect(offenders).toEqual([]);
140+
});
141+
142+
it('lit control: the permission hint names the environment overlay in every table', () => {
143+
const silent: string[] = [];
144+
for (const [name, table] of tablesCarryingKeys()) {
145+
const value = table.get(PERMISSION_HINT) ?? '';
146+
if (!ENVIRONMENT_OVERLAY.test(value)) silent.push(`${name}: ${value}`);
147+
}
115148
expect(silent).toEqual([]);
116149
});
117150
});

‎ui/packages/app-shell/src/views/studio-design/BuilderLanding.tsx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,11 @@ export function BuilderLanding(): React.ReactElement {
217217
// Duplicate (ADR-0070 D4) clones a writable BASE into a new writable package,
218218
// the "duplicate base" gesture, and drops the user straight into the copy's
219219
// builder. It is offered on writable bases only. A read-only code or
220-
// installed package is not a duplicate source: ADR-0070 D2 customizes it by
221-
// environment overlay, where the item's metadata type declares `allowEnvironmentOverlay`
222-
// (ADR-0005), so its card points there instead (the read-only section
223-
// below). Home's "Start with a template" is not this gesture either: it
224-
// opens the marketplace.
220+
// installed package is not a duplicate source: its items are customized in
221+
// its paired `.custom` package through Customize or New similar (objectstack
222+
// ADR-0146 D2, D3), never by a package-less overlay (D11), and the read-only
223+
// section below says so. Home's "Start with a template" is not this gesture
224+
// either: it opens the marketplace.
225225
const [dupFor, setDupFor] = React.useState<string | null>(null);
226226
const [dupName, setDupName] = React.useState('');
227227
const [dupId, setDupId] = React.useState('');
@@ -327,7 +327,7 @@ export function BuilderLanding(): React.ReactElement {
327327
{/* ADR-0070 D4 — Duplicate clones a writable base: the copy is a new
328328
* writable package holding the base's sys_metadata rows. It is
329329
* offered here, on writable bases only; a read-only package's card
330-
* offers the routes that exist for it (environment overlay, the
330+
* offers the routes that exist for it (its `.custom` pair, the
331331
* marketplace). */}
332332
<button
333333
type="button"

‎ui/packages/i18n/src/namespaces/studio/en.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3234,7 +3234,7 @@ const STUDIO_EN: Record<string, string> = {
32343234
'Saved as a draft in this deployment’s customization of {baseName}, and opened on the canvas. The navigation of {baseName} stays as it was installed.',
32353235
'engine.studio.app.scaffoldNav': "Add this package's objects as menu items",
32363236
'engine.studio.landing.title': 'App Builder',
3237-
'engine.studio.landing.description': 'Design objects, forms, automations and interfaces inside a writable package; edits save as drafts and the whole package publishes in one release. Code and installed packages are read-only: browse them here, and customize them with an environment overlay.',
3237+
'engine.studio.landing.description': 'Design objects, forms, automations and interfaces inside a writable package; edits save as drafts and the whole package publishes in one release. Code and installed packages are read-only: browse them here, and change their items with Customize or New similar, which save into the package’s .custom customization package.',
32383238
'engine.studio.landing.mineHeading': 'My packages (writable)',
32393239
'engine.studio.landing.noneWritable': 'No writable packages yet — create one to start.',
32403240
'engine.studio.landing.dup': 'Duplicate',
@@ -3245,9 +3245,10 @@ const STUDIO_EN: Record<string, string> = {
32453245
'engine.studio.landing.dupGo': 'Duplicate and open the builder',
32463246
'engine.studio.landing.dupCreated': 'Duplicated into writable package “{name}”',
32473247
'engine.studio.landing.installedHeading': 'Installed (read-only · browsable)',
3248-
// objectui#11808 — the routes a read-only package does have (ADR-0070 D2,
3249-
// ADR-0005): an environment overlay, and the marketplace where the runtime has one.
3250-
'engine.studio.landing.readonlyHint': 'Code and installed packages are read-only, so they are not edited or duplicated here. Customize one with an environment overlay, for the metadata types that accept one.',
3248+
// The routes a read-only package does have (objectstack ADR-0146 D2, D3, D11):
3249+
// Customize or New similar into its paired `.custom` package, and the
3250+
// marketplace where the runtime has one. A package-less overlay is not one.
3251+
'engine.studio.landing.readonlyHint': 'Code and installed packages are read-only, so they are not edited or duplicated here. To change one, open an item and choose Customize or New similar: the change is saved in the package’s .custom customization package, and upgrades keep reaching the installed package.',
32513252
'engine.studio.landing.marketplace': 'Or install a template app from the marketplace',
32523253
// ADR-0146 D3 — a read-only base's card says what this deployment changed on
32533254
// it, from the server's per-package customization read.

‎ui/packages/i18n/src/namespaces/studio/zh.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2862,7 +2862,7 @@ const STUDIO_ZH: Record<string, string> = {
28622862
'engine.studio.interfaces.create.descriptionInPair': '存为本部署对 {baseName} 定制的草稿,并在画布上打开。{baseName} 的导航保持安装时的样子。',
28632863
'engine.studio.app.scaffoldNav': '将本包的对象添加为菜单项',
28642864
'engine.studio.landing.title': '应用构建',
2865-
'engine.studio.landing.description': '在一个可写软件包里设计对象、表单、自动化与界面;编辑存为草稿,整包一次发布。源码与已安装的软件包为只读:可在此浏览,并用环境覆盖进行定制。',
2865+
'engine.studio.landing.description': '在一个可写软件包里设计对象、表单、自动化与界面;编辑存为草稿,整包一次发布。源码与已安装的软件包为只读:可在此浏览,并用「自定义」或「新建类似」修改其条目,修改保存在该软件包的 .custom 定制包中。',
28662866
'engine.studio.landing.mineHeading': '我的软件包(可写)',
28672867
'engine.studio.landing.noneWritable': '还没有可写软件包 — 新建一个开始。',
28682868
'engine.studio.landing.dup': '复制',
@@ -2874,7 +2874,7 @@ const STUDIO_ZH: Record<string, string> = {
28742874
'engine.studio.landing.dupCreated': '已复制为可写软件包「{name}」',
28752875
'engine.studio.landing.installedHeading': '已安装(只读 · 可浏览)',
28762876
// objectui#11808 — the routes a read-only package does have.
2877-
'engine.studio.landing.readonlyHint': '源码与已安装的软件包为只读,不能在此编辑或复制。对于接受环境覆盖的元数据类型,可用环境覆盖(overlay)进行定制。',
2877+
'engine.studio.landing.readonlyHint': '源码与已安装的软件包为只读,不能在此编辑或复制。要修改其中一个,请打开条目并选择「自定义」或「新建类似」:修改保存在该软件包的 .custom 定制包中,升级仍会到达已安装的软件包。',
28782878
'engine.studio.landing.marketplace': '或从应用市场安装一个模板应用',
28792879
'engine.studio.landing.customizations.customized': '已定制 {customized} 项',
28802880
'engine.studio.landing.customizations.separator': ',',

0 commit comments

Comments
 (0)