@object-ui/app-shell 11.3.0 → 11.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +522 -0
- package/README.md +23 -0
- package/dist/console/ConsoleShell.js +17 -2
- package/dist/console/home/CloudOnboardingNext.d.ts +9 -0
- package/dist/console/home/CloudOnboardingNext.js +14 -4
- package/dist/console/home/HomePage.js +34 -7
- package/dist/console/organizations/CreateWorkspaceDialog.js +33 -3
- package/dist/console/organizations/OrganizationsPage.js +16 -7
- package/dist/hooks/useConsoleActionRuntime.js +32 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/preview/DraftChangesPanel.d.ts +3 -1
- package/dist/preview/DraftChangesPanel.js +6 -5
- package/dist/utils/deriveRelatedLists.d.ts +20 -5
- package/dist/utils/deriveRelatedLists.js +31 -13
- package/dist/utils/resolveViewId.d.ts +23 -0
- package/dist/utils/resolveViewId.js +37 -0
- package/dist/utils/warnSuppressedListNav.d.ts +10 -0
- package/dist/utils/warnSuppressedListNav.js +40 -0
- package/dist/views/InterfaceListPage.js +6 -4
- package/dist/views/ObjectView.js +61 -10
- package/dist/views/RecordDetailView.js +131 -104
- package/dist/views/RecordFormPage.js +7 -1
- package/dist/views/RelatedRecordActionsBridge.d.ts +24 -0
- package/dist/views/RelatedRecordActionsBridge.js +114 -0
- package/dist/views/metadata-admin/PackagesPage.js +18 -7
- package/dist/views/metadata-admin/PermissionMatrixEditor.d.ts +18 -1
- package/dist/views/metadata-admin/PermissionMatrixEditor.js +73 -14
- package/dist/views/metadata-admin/i18n.d.ts +12 -21
- package/dist/views/metadata-admin/i18n.js +343 -2
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +25 -11
- package/dist/views/metadata-admin/permission-slice.d.ts +66 -0
- package/dist/views/metadata-admin/permission-slice.js +70 -0
- package/dist/views/metadata-admin/previews/AppNavCanvas.js +11 -7
- package/dist/views/metadata-admin/previews/FlowRunsPanel.d.ts +16 -7
- package/dist/views/metadata-admin/previews/FlowRunsPanel.js +18 -2
- package/dist/views/studio-design/BuilderLanding.d.ts +15 -0
- package/dist/views/studio-design/BuilderLanding.js +133 -0
- package/dist/views/studio-design/ObjectFormDesigner.d.ts +31 -0
- package/dist/views/studio-design/ObjectFormDesigner.js +226 -0
- package/dist/views/studio-design/ObjectSettingsPanel.d.ts +30 -0
- package/dist/views/studio-design/ObjectSettingsPanel.js +45 -0
- package/dist/views/studio-design/ObjectValidationsPanel.d.ts +30 -0
- package/dist/views/studio-design/ObjectValidationsPanel.js +78 -0
- package/dist/views/studio-design/StudioDesignSurface.js +793 -146
- package/dist/views/studio-design/metadataError.d.ts +23 -0
- package/dist/views/studio-design/metadataError.js +44 -0
- package/dist/views/studio-design/packages-io.d.ts +27 -0
- package/dist/views/studio-design/packages-io.js +61 -0
- package/package.json +42 -39
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ObjectUI
|
|
4
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* RelatedRecordActionsBridge — supplies the console's object-aware CRUD +
|
|
11
|
+
* action handlers to the `record:related_list` renderers on a detail page.
|
|
12
|
+
*
|
|
13
|
+
* The renderer (in `@object-ui/plugin-detail`) knows the child object, the FK
|
|
14
|
+
* back to the parent, and the parent id — but not the SPA routes, the
|
|
15
|
+
* create/edit form pages, or the per-object lifecycle affordances. This bridge
|
|
16
|
+
* (mounted inside the page's `ActionProvider`) closes that gap:
|
|
17
|
+
*
|
|
18
|
+
* - 查看详情 → navigate to the child record's detail route
|
|
19
|
+
* - 增 → navigate to the child's `/new` page, pre-linking the parent
|
|
20
|
+
* via `?<relationshipField>=<parentId>` (the convention
|
|
21
|
+
* RecordFormPage already reads as create-mode initial values)
|
|
22
|
+
* - 改 → navigate to the child record's `/edit` page
|
|
23
|
+
* - 删 → `dataSource.delete(child, id)` (RelatedList shows the confirm
|
|
24
|
+
* dialog and refreshes afterwards)
|
|
25
|
+
* - 子对象 action → the child object's `list_item` actions, executed against
|
|
26
|
+
* the clicked row through the page's shared ActionRunner
|
|
27
|
+
*
|
|
28
|
+
* Each affordance is gated by {@link resolveCrudAffordances} so system /
|
|
29
|
+
* append-only children never show New / Edit / Delete. When this bridge is
|
|
30
|
+
* absent (e.g. the Studio designer) the related list stays read-only.
|
|
31
|
+
*/
|
|
32
|
+
import { useCallback, useMemo } from 'react';
|
|
33
|
+
import { useNavigate } from 'react-router-dom';
|
|
34
|
+
import { RelatedRecordActionsProvider, useAction, } from '@object-ui/react';
|
|
35
|
+
import { resolveCrudAffordances } from '../utils/crudAffordances';
|
|
36
|
+
/** Notify open related lists for `objectName` to refetch (see RelatedList). */
|
|
37
|
+
export function notifyRelatedChanged(objectName) {
|
|
38
|
+
if (typeof window === 'undefined')
|
|
39
|
+
return;
|
|
40
|
+
window.dispatchEvent(new CustomEvent('objectui:related-changed', { detail: { objectName } }));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Derive the child object's row actions (metadata `actions` filtered to the
|
|
44
|
+
* `list_item` location), localized and shaped for the related-list row menu.
|
|
45
|
+
*/
|
|
46
|
+
function deriveRowActions(childDef, actionLabel) {
|
|
47
|
+
const actions = Array.isArray(childDef?.actions) ? childDef.actions : [];
|
|
48
|
+
return actions
|
|
49
|
+
.filter((a) => Array.isArray(a?.locations) && a.locations.includes('list_item'))
|
|
50
|
+
.map((a) => ({
|
|
51
|
+
...a,
|
|
52
|
+
label: actionLabel(childDef.name, a.name, a.label || a.name),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
export function RelatedRecordActionsBridge({ appName, objects, dataSource, actionLabel, children, }) {
|
|
56
|
+
const navigate = useNavigate();
|
|
57
|
+
const { execute } = useAction();
|
|
58
|
+
const base = appName ? `/apps/${appName}` : '';
|
|
59
|
+
// Execute a child object's row action against the clicked record. Reuses the
|
|
60
|
+
// page's ActionRunner (confirm dialog, toast, param collection are handled by
|
|
61
|
+
// it) but retargets it at the CHILD object + row via the action's
|
|
62
|
+
// `objectName` / `recordId`, which the record-detail action handlers honor.
|
|
63
|
+
const runRowAction = useCallback(async (childObject, record, action) => {
|
|
64
|
+
const id = record?.id ?? record?._id;
|
|
65
|
+
const def = {
|
|
66
|
+
...action,
|
|
67
|
+
objectName: childObject,
|
|
68
|
+
...(id != null ? { recordId: String(id) } : {}),
|
|
69
|
+
params: { ...action.params },
|
|
70
|
+
};
|
|
71
|
+
const res = await execute(def);
|
|
72
|
+
// Refresh open related lists for this child object after a successful
|
|
73
|
+
// mutating action (the row menu handler is otherwise fire-and-forget).
|
|
74
|
+
if (res?.success)
|
|
75
|
+
notifyRelatedChanged(childObject);
|
|
76
|
+
}, [execute]);
|
|
77
|
+
const value = useMemo(() => ({
|
|
78
|
+
resolve: ({ objectName, relationshipField, parentId }) => {
|
|
79
|
+
const childDef = objects.find((o) => o?.name === objectName);
|
|
80
|
+
if (!childDef || !base)
|
|
81
|
+
return {};
|
|
82
|
+
const aff = resolveCrudAffordances(childDef);
|
|
83
|
+
const detailUrl = (id) => `${base}/${objectName}/record/${encodeURIComponent(String(id))}`;
|
|
84
|
+
const handlers = {
|
|
85
|
+
// Viewing a child record is always allowed when the list is visible.
|
|
86
|
+
onView: (id) => navigate(detailUrl(id)),
|
|
87
|
+
};
|
|
88
|
+
if (aff.create) {
|
|
89
|
+
handlers.onCreate = () => {
|
|
90
|
+
const canLink = relationshipField && parentId != null && parentId !== '';
|
|
91
|
+
const qs = canLink
|
|
92
|
+
? `?${encodeURIComponent(relationshipField)}=${encodeURIComponent(String(parentId))}`
|
|
93
|
+
: '';
|
|
94
|
+
navigate(`${base}/${objectName}/new${qs}`);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (aff.edit) {
|
|
98
|
+
handlers.onEdit = (id) => navigate(`${detailUrl(id)}/edit`);
|
|
99
|
+
}
|
|
100
|
+
if (aff.delete) {
|
|
101
|
+
handlers.onDelete = async (id) => {
|
|
102
|
+
await dataSource?.delete?.(objectName, String(id));
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const rowActions = deriveRowActions(childDef, actionLabel);
|
|
106
|
+
if (rowActions.length > 0) {
|
|
107
|
+
handlers.rowActions = rowActions;
|
|
108
|
+
handlers.onRowAction = (action, record) => runRowAction(objectName, record, action);
|
|
109
|
+
}
|
|
110
|
+
return handlers;
|
|
111
|
+
},
|
|
112
|
+
}), [objects, base, navigate, dataSource, actionLabel, runRowAction]);
|
|
113
|
+
return (_jsx(RelatedRecordActionsProvider, { value: value, children: children }));
|
|
114
|
+
}
|
|
@@ -44,16 +44,23 @@ async function apiJson(path, init) {
|
|
|
44
44
|
/* -------------------------------------------------------------------------- */
|
|
45
45
|
function ScopeBadge({ scope }) {
|
|
46
46
|
const locale = React.useMemo(() => detectLocale(), []);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// Writability semantics, aligned with the builder (studio-design/packages-io):
|
|
48
|
+
// a SCOPE-LESS entry is a database base package (writable — authoring lives
|
|
49
|
+
// there), while `project` marks a read-only code package. Defaulting the
|
|
50
|
+
// missing scope to 'project' used to render both with the same badge, which
|
|
51
|
+
// contradicted the builder's 可写/只读 labeling for the very same package.
|
|
52
|
+
if (!scope) {
|
|
53
|
+
return (_jsx(Badge, { className: "bg-emerald-400/15 text-emerald-600 hover:bg-emerald-400/15 dark:text-emerald-300", children: t('engine.packages.scope.writable', locale) }));
|
|
54
|
+
}
|
|
55
|
+
const variant = scope === 'project' ? 'default' : scope === 'system' ? 'secondary' : 'outline';
|
|
56
|
+
const labelKey = scope === 'project'
|
|
50
57
|
? 'engine.packages.scope.project'
|
|
51
|
-
:
|
|
58
|
+
: scope === 'system'
|
|
52
59
|
? 'engine.packages.scope.system'
|
|
53
|
-
:
|
|
60
|
+
: scope === 'cloud'
|
|
54
61
|
? 'engine.packages.scope.cloud'
|
|
55
62
|
: '';
|
|
56
|
-
return _jsx(Badge, { variant: variant, children: labelKey ? t(labelKey, locale) :
|
|
63
|
+
return _jsx(Badge, { variant: variant, children: labelKey ? t(labelKey, locale) : scope });
|
|
57
64
|
}
|
|
58
65
|
function StatusBadge({ pkg }) {
|
|
59
66
|
const locale = React.useMemo(() => detectLocale(), []);
|
|
@@ -99,7 +106,11 @@ export function CreatePackageDialog({ open, onOpenChange, onCreated, }) {
|
|
|
99
106
|
name: name.trim(),
|
|
100
107
|
version: version.trim(),
|
|
101
108
|
type: 'app',
|
|
102
|
-
scope
|
|
109
|
+
// No `scope`: runtime-created base packages are writable authoring
|
|
110
|
+
// targets. `scope: 'project'` marks read-only CODE packages — the
|
|
111
|
+
// old hardcode here made Setup-created bases read as 只读 in the
|
|
112
|
+
// builder's switcher/landing while the builder's own creator made
|
|
113
|
+
// writable ones. One creation semantic everywhere now.
|
|
103
114
|
},
|
|
104
115
|
}),
|
|
105
116
|
});
|
|
@@ -35,5 +35,22 @@ import * as React from 'react';
|
|
|
35
35
|
export interface PermissionMatrixEditPageProps {
|
|
36
36
|
type: string;
|
|
37
37
|
name: string;
|
|
38
|
+
/**
|
|
39
|
+
* When set, the matrix is scoped to a single package (ADR-0086 P0): it lists
|
|
40
|
+
* only the objects that package declares, and Save merges just that slice
|
|
41
|
+
* back — other packages' contributed rows are left untouched. When omitted,
|
|
42
|
+
* the matrix operates at environment scope (all objects, whole-record save).
|
|
43
|
+
*/
|
|
44
|
+
packageId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* ADR-0086 P2 (D6/D7 — the package door). When editing under a `packageId`,
|
|
47
|
+
* a permission set is package **metadata**: Save writes a **draft** (not a
|
|
48
|
+
* live record), published atomically with the rest of the package. `onDraftSaved`
|
|
49
|
+
* notifies the surface so its pending-changes counter refreshes; `publishNonce`
|
|
50
|
+
* bumps on publish so the editor re-reads the now-published baseline (its draft
|
|
51
|
+
* is gone). Both are no-ops at environment scope, where Save stays live (D7).
|
|
52
|
+
*/
|
|
53
|
+
onDraftSaved?: () => void;
|
|
54
|
+
publishNonce?: number;
|
|
38
55
|
}
|
|
39
|
-
export declare function PermissionMatrixEditPage({ type, name }: PermissionMatrixEditPageProps): React.JSX.Element;
|
|
56
|
+
export declare function PermissionMatrixEditPage({ type, name, packageId, onDraftSaved, publishNonce }: PermissionMatrixEditPageProps): React.JSX.Element;
|
|
@@ -48,6 +48,7 @@ import { useMetadataClient, useMetadataTypes } from './useMetadata';
|
|
|
48
48
|
import { resolveResourceConfig } from './registry';
|
|
49
49
|
import { t as translate, detectLocale } from './i18n';
|
|
50
50
|
import { AssignedUsersSection } from './AssignedUsersSection';
|
|
51
|
+
import { mergePermissionSlice, scopePermissionSet, } from './permission-slice';
|
|
51
52
|
function getObjectActions(locale) {
|
|
52
53
|
return [
|
|
53
54
|
{ key: 'allowCreate', short: 'C', tip: translate('perm.action.create', locale) },
|
|
@@ -64,7 +65,7 @@ function getObjectActions(locale) {
|
|
|
64
65
|
/* ────────────────────────────────────────────────────────────────── */
|
|
65
66
|
/* Component */
|
|
66
67
|
/* ────────────────────────────────────────────────────────────────── */
|
|
67
|
-
export function PermissionMatrixEditPage({ type, name }) {
|
|
68
|
+
export function PermissionMatrixEditPage({ type, name, packageId, onDraftSaved, publishNonce }) {
|
|
68
69
|
const navigate = useNavigate();
|
|
69
70
|
const client = useMetadataClient();
|
|
70
71
|
const { entries } = useMetadataTypes(client);
|
|
@@ -94,20 +95,27 @@ export function PermissionMatrixEditPage({ type, name }) {
|
|
|
94
95
|
setLoading(true);
|
|
95
96
|
(async () => {
|
|
96
97
|
try {
|
|
97
|
-
const [lay, objList] = await Promise.all([
|
|
98
|
+
const [lay, objList, pendingDraft] = await Promise.all([
|
|
98
99
|
client.layered(type, name).catch(() => null),
|
|
99
|
-
|
|
100
|
+
// In package scope, list only the objects this package declares
|
|
101
|
+
// (ADR-0086 P0) — otherwise the whole environment leaks into the panel.
|
|
102
|
+
client.list('object', packageId ? { packageId } : {}).catch(() => []),
|
|
103
|
+
// ADR-0086 P2 (D6): under the package door a set is draft/published
|
|
104
|
+
// metadata, so surface the PENDING draft if one exists — otherwise a
|
|
105
|
+
// just-saved-not-yet-published edit would appear lost on reopen. Draft
|
|
106
|
+
// reads return the `{ type, name, item }` envelope; `null` = no draft.
|
|
107
|
+
packageId
|
|
108
|
+
? client.getDraft(type, name, { packageId }).catch(() => null)
|
|
109
|
+
: Promise.resolve(null),
|
|
100
110
|
]);
|
|
101
111
|
if (cancelled)
|
|
102
112
|
return;
|
|
103
|
-
const
|
|
113
|
+
const draftBody = pendingDraft
|
|
114
|
+
? (pendingDraft.item ?? pendingDraft)
|
|
115
|
+
: null;
|
|
116
|
+
// Draft wins over the published baseline for display (D6).
|
|
117
|
+
const effective = (draftBody ?? lay?.effective ??
|
|
104
118
|
lay?.code ?? { name, objects: {} });
|
|
105
|
-
setDraft({
|
|
106
|
-
...effective,
|
|
107
|
-
name: String(effective?.name ?? name),
|
|
108
|
-
objects: effective?.objects ?? {},
|
|
109
|
-
fields: effective?.fields ?? {},
|
|
110
|
-
});
|
|
111
119
|
const list = (objList ?? [])
|
|
112
120
|
.map((row) => {
|
|
113
121
|
const item = row?.item ?? row;
|
|
@@ -116,6 +124,22 @@ export function PermissionMatrixEditPage({ type, name }) {
|
|
|
116
124
|
.filter((o) => !!o.name)
|
|
117
125
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
118
126
|
setObjects(list);
|
|
127
|
+
const full = {
|
|
128
|
+
...effective,
|
|
129
|
+
name: String(effective?.name ?? name),
|
|
130
|
+
objects: effective?.objects ?? {},
|
|
131
|
+
fields: effective?.fields ?? {},
|
|
132
|
+
};
|
|
133
|
+
// Package scope: only surface this package's slice for editing; rows
|
|
134
|
+
// contributed by other packages stay off-screen and are re-merged on
|
|
135
|
+
// Save from a fresh read (see doSave).
|
|
136
|
+
if (packageId) {
|
|
137
|
+
const sliced = scopePermissionSet(full, list.map((o) => o.name));
|
|
138
|
+
setDraft({ ...full, objects: sliced.objects, fields: sliced.fields });
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
setDraft(full);
|
|
142
|
+
}
|
|
119
143
|
}
|
|
120
144
|
catch (err) {
|
|
121
145
|
setError(err?.message ?? String(err));
|
|
@@ -128,7 +152,7 @@ export function PermissionMatrixEditPage({ type, name }) {
|
|
|
128
152
|
return () => {
|
|
129
153
|
cancelled = true;
|
|
130
154
|
};
|
|
131
|
-
}, [client, type, name]);
|
|
155
|
+
}, [client, type, name, packageId, publishNonce]);
|
|
132
156
|
/* ── Lazy-load fields when an object is expanded ─────────── */
|
|
133
157
|
async function ensureFields(objectName) {
|
|
134
158
|
if (fieldsByObject[objectName])
|
|
@@ -211,17 +235,52 @@ export function PermissionMatrixEditPage({ type, name }) {
|
|
|
211
235
|
return { ...prev, fields };
|
|
212
236
|
});
|
|
213
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Re-narrow a freshly-read full permission set to this package's slice for
|
|
240
|
+
* display. No-op at environment scope (no `packageId`).
|
|
241
|
+
*/
|
|
242
|
+
function toDisplayDraft(set) {
|
|
243
|
+
if (!packageId)
|
|
244
|
+
return set;
|
|
245
|
+
const sliced = scopePermissionSet(set, objects.map((o) => o.name));
|
|
246
|
+
return { ...set, objects: sliced.objects, fields: sliced.fields };
|
|
247
|
+
}
|
|
214
248
|
/* ── Save ────────────────────────────────────────────────── */
|
|
215
249
|
async function doSave(force, pending) {
|
|
216
250
|
const payload = pending ?? draft;
|
|
217
251
|
setSaving(true);
|
|
218
252
|
setError(null);
|
|
219
253
|
try {
|
|
220
|
-
|
|
254
|
+
// Package scope: merge only this package's slice back onto a fresh read
|
|
255
|
+
// of the record so rows contributed by other packages survive byte-for-
|
|
256
|
+
// byte (ADR-0086 P0). Environment scope keeps the whole-record save.
|
|
257
|
+
let toSave = payload;
|
|
258
|
+
if (packageId) {
|
|
259
|
+
const scope = objects.map((o) => o.name);
|
|
260
|
+
const fresh = await client
|
|
261
|
+
.layered(type, payload.name)
|
|
262
|
+
.catch(() => null);
|
|
263
|
+
const base = (fresh?.effective ?? payload);
|
|
264
|
+
toSave = mergePermissionSlice(base, payload, scope);
|
|
265
|
+
}
|
|
266
|
+
// ADR-0086 P2 (D6/D7). Package door → the set is metadata: write a DRAFT
|
|
267
|
+
// (stamped with `packageId`) that the package's atomic Publish promotes,
|
|
268
|
+
// exactly like the Data/Interfaces pillars — NOT a live record write.
|
|
269
|
+
// Environment door (no packageId) stays live (config).
|
|
270
|
+
await client.save(type, payload.name, toSave, {
|
|
221
271
|
force,
|
|
272
|
+
...(packageId ? { mode: 'draft', packageId } : {}),
|
|
222
273
|
});
|
|
223
|
-
|
|
224
|
-
|
|
274
|
+
if (packageId) {
|
|
275
|
+
// The draft is now the pending truth for display; the published baseline
|
|
276
|
+
// hasn't moved. Show what we just staged and let the surface count it.
|
|
277
|
+
setDraft(toDisplayDraft(toSave));
|
|
278
|
+
onDraftSaved?.();
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const lay = await client.layered(type, payload.name);
|
|
282
|
+
setDraft(toDisplayDraft((lay.effective ?? toSave)));
|
|
283
|
+
}
|
|
225
284
|
setDestructive(null);
|
|
226
285
|
}
|
|
227
286
|
catch (err) {
|
|
@@ -1,24 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Metadata admin i18n bundle (Phase 3f).
|
|
3
|
-
*
|
|
4
|
-
* Lightweight static label table for the 27 built-in metadata types,
|
|
5
|
-
* plus a tiny `t()` helper for engine UI strings.
|
|
6
|
-
*
|
|
7
|
-
* Why not i18next? The engine already consumes `label` from the
|
|
8
|
-
* server's `/meta/types` response (which is sourced from
|
|
9
|
-
* `DEFAULT_METADATA_TYPE_REGISTRY`). This bundle exists as a fallback
|
|
10
|
-
* for environments without translation bundles configured, and as the
|
|
11
|
-
* single source of truth for Chinese labels until the platform's
|
|
12
|
-
* `setup.translation.ts` ships zh-CN coverage.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* import { translateMetadataType, t } from './i18n';
|
|
16
|
-
* translateMetadataType('view', 'zh-CN') // → '视图'
|
|
17
|
-
* t('engine.directory.title', 'zh-CN') // → '元数据'
|
|
18
|
-
*
|
|
19
|
-
* The DirectoryPage / PageShell call these to localise headings when
|
|
20
|
-
* the consumer hasn't wired the global i18n provider.
|
|
21
|
-
*/
|
|
22
1
|
export type SupportedLocale = 'en-US' | 'zh-CN';
|
|
23
2
|
export declare function translateMetadataType(type: string, locale?: SupportedLocale | string, fallback?: string): string;
|
|
24
3
|
export declare function translateMetadataDomain(domain: string, locale?: SupportedLocale | string): string;
|
|
@@ -32,3 +11,15 @@ export declare function tFormat(key: string, locale: SupportedLocale | string |
|
|
|
32
11
|
export declare function translateValidationMessage(message: string | undefined, locale?: SupportedLocale | string): string;
|
|
33
12
|
/** Returns the locale string most browsers report (matches navigator.language). */
|
|
34
13
|
export declare function detectLocale(): SupportedLocale;
|
|
14
|
+
/**
|
|
15
|
+
* React hook — the Studio/metadata-admin locale derived from the app's ACTIVE
|
|
16
|
+
* i18n language, collapsed to one of the two locales this catalog ships.
|
|
17
|
+
*
|
|
18
|
+
* Prefer this over {@link detectLocale} in components: `detectLocale()` reads
|
|
19
|
+
* `navigator.language`, which never changes when the user switches languages
|
|
20
|
+
* in-app via the LocaleSwitcher (that only moves the i18next instance). This
|
|
21
|
+
* follows the live `useObjectTranslation().language`, so the Studio pillars
|
|
22
|
+
* re-render in lock-step with the console locale — the same source the rest of
|
|
23
|
+
* the app (Home, forms, list views) renders from.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useMetadataLocale(): SupportedLocale;
|